Realm在多线程场景中给出了陈旧的结果

时间:2016-11-18 10:48:21

标签: android realm

方案

我有一个典型的UI线程和工作线程场景。我做了一些工作,并将结果写入工作线程中的领域。结果是一个带有一些String字段的简单RealmObject。完成后,我将UI线程上的事件发送到我的Activity(使用Otto事件总线)来报告工作已完成。

在我的Activity中接收到事件后,我查询结果,并且字符串字段未使用写入值更新。

在工作线程上:

// 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());

活动:

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

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

我做了什么

我意识到如果我将查询包装在事务块中,那么当我尝试打印它时,RealmObject将是最新的。

// 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);

问题

  1. 获取最新RealmObject的正确方法是什么?我是否必须每次都将它们放入事务块中以强制它与工作线程“同步”?是否有我可以遵循的模式?

  2. 开始一个领域事务(通过Realm#beginTransaction()或Realm#executeTransaction())究竟做了什么?它阻止其他线程的读/写尝试吗?在交易中执行长期操作(例如网络请求)是否有任何损害?

  3. 修改

    实际代码:

    // 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()); 
    }
    

2 个答案:

答案 0 :(得分:2)

UI线程的Realm由后台守护程序线程更新,而不是自2.0.0以来直接发送到Looper消息队列的消息

即使在2.0.0之前,异步查询也会延迟结果的同步版本更新,直到评估完所有异步查询。

所以Otto直接发送消息,并且只有在您使用同步查询(.findAll()findAllSorted()等时,才会立即期望UI线程保持最新状态。 。),并使用Realm 1.2.0或Realm 1.1.1或更早版本。

规范的解决方案是使用RealmChangeListener来通知变更。

非官方的解决方案是强制Realm直接刷新自身及其所有结果,但这很麻烦并迫使异步查询同步执行,所以并不是真的推荐。

对于事务,它阻止所有其他线程执行事务,直到完成当前事务。在事务中,您始终可以看到最新版本的Realm。

答案 1 :(得分:1)

Realm使用特殊的“侦听器”线程与其他线程进行通信,该线程将消息放入UI线程Looper队列中。我们不会在发生这种情况时提供任何保证,因为它可能由于多种原因而延迟。 Otto将直接发送消息,这很可能会在looper消息到达之前发生。在这种情况下,UI线程上的数据将显示为“陈旧”。

对这些类型的通知使用Realm更改侦听器要好得多。在这种情况下,您将在数据准备就绪时收到通知。

另请参阅:https://github.com/realm/realm-java/issues/3427