【问题标题】:Realm give stale results in multi thread scenario领域在多线程场景中给出过时的结果
【发布时间】:2016-11-18 23:38:16
【问题描述】:

场景

我有一个典型的 UI 线程和工作线程场景。我做了一些工作并将结果写入工作线程中的领域。结果是一个带有一些字符串字段的简单 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())究竟是做什么的?它会阻止其他线程的读/写尝试吗?在事务中执行长操作(如网络请求)有什么危害吗?

编辑

实际代码:

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

【问题讨论】:

  • 尝试使用 setSomething() 方法。我相信领域代理对象使用它。不确定,但值得一试
  • @Tim 我在实际代码中使用了 setSomething() 方法。结果还是一样。给出的示例仅用于说明目的。
  • 一条建议:发布您的实际代码
  • @Tim 你去吧
  • 谢谢。顺便说一句,只需删除其他代码。它会让人们感到困惑

标签: android realm


【解决方案1】:

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

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

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


规范的解决方案是使用RealmChangeListeners 来代替收到更改通知。

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


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

【讨论】:

    【解决方案2】:

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

    最好使用 Realm 更改侦听器来处理这些类型的通知。在这种情况下,您会在数据准备就绪时收到通知。

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

    【讨论】:

    • 嗨,克里斯蒂安,感谢您的回答。第二个问题呢?事务会阻塞另一个线程的读/写吗?
    • 写事务在所有线程中相互阻塞。读取事务永远不会被阻止(即使正在进行写入事务,您也可以随时访问您的数据)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 2016-09-19
    • 1970-01-01
    相关资源
    最近更新 更多