Weizhi Weizhi - 16 days ago 7
Android Question

Realm give stale results in multi thread scenario

Scenario



I have a typical UI thread and worker thread scenario. I did some work and write the result to realm in worker thread. The result is a simple RealmObject with some String fields. Once this is done, I send an event on UI thread to my Activity (using Otto event bus) to report that the work is completed.

Upon receiving event in my Activity, I query for the result and the String fields were not updated with the write value.

On worker thread:

// Did some work. Got some result

// Write to realm
try {
realm = Realm.getDefaultInstance();

realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
MyResult result = realm.where(MyResult.class)
.equalTo("id", 1)
.findFirst();
result.someString = "hello world";
}
});
} finally {
if(realm != null){
realm.close();
realm = null;
}
}

//Post job done event on Otto bus
uiThreadBus.post(new JobDoneEvent());


On Activity:

// Upon received JobDoneEvent
MyResult result = realm.where(MyResult.class)
.equalTo("id", 1)
.findFirst();

// result.someString is some stale value
Log.d("TAG", result.someString);


What I did



I realized if i wrap the query in a transaction block then the RealmObject will be up to date when i try to print it.

// Upon received JobDoneEvent
MyResult result = null;
try{
realm.beginTransaction();
result = realm.where(MyResult.class)
.equalTo("id", 1)
.findFirst();
realm.cancelTransaction();
}
catch(Exception e) {
realm.cancelTransaction();
}

// result.someString is up-to-date
Log.d("TAG", result.someString);


Questions




  1. What is the correct way to get up-to-date RealmObject? Do i have to throw them in a transaction block every time to force it to "synchronize" with worker thread? Is there a pattern I can follow?

  2. What exactly does beginning a realm transaction (either through Realm#beginTransaction() or Realm#executeTransaction()) do? Does it block read/write attempt by other thread? Is there any harm in performing long operation (such as network request) in a transaction?



Edit



Actual code:

// Did some work. Got some result

// Write to realm
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
User managedUser = result.payload.createOrUpdateInRealm(realm,
MyApplication.getPrimaryKeyFactory());

Log.i("TAG", "updated user: " + managedUser.getId());
}
});
} finally {
if(realm != null) {
realm.close();
realm = null;
}
}

//Post job done event on Otto bus
MyApplication.getBusInstance().post(new LoginEvent());



// Writing to realm method
public User createOrUpdateInRealm(@NonNull Realm realm,
@NonNull PrimaryKeyFactory pkFactory) {
User managedUser = realm.where(User.class)
.equalTo("primary_key", pk)
.findFirst();

managedUser.setId(xUserId);
return managedUser;
}


// Event receiving method in Activity
@Subscribe
public void loginEventReceived(LoginEvent event) {
User user = mRealm.where(User.class)
.equalTo("primary_key", mPk)
.findFirst();

Log.d("TAG", user.getId());
}

Answer

Realm communicate with other threads using a special "listener" thread that put messages into the UI threads Looper queue. We don't provide any guarantees when this will happen as it might be delayed for a number of reasons. Otto will send a message directly, which most likely will happen before the looper message arrived. In that case the data on the UI thread will appear "stale".

It is much better to use Realm change listeners for these kinds of notifications. In that case you will be notified when the data is ready.

See also: https://github.com/realm/realm-java/issues/3427

Comments