在 Realm 中并行读写的结果不一致
Inconsistent results with parallel reads & writes in Realm
也许我遗漏了一些东西,但我在从 Realm 并行读取和写入同一对象时得到了一些奇怪的结果。
我第一次遇到这个问题是在一个更大的项目中,但现在才设法在测试项目中重现它。
场景:创建一个具有两个 DateTimeOffset
字段的 RealmObject
对象,然后每分钟更新一次。另一个线程每 10 秒读取一次并输出值。
我将首先显示输出,因为它最相关。括号中的时间是记录输出的时间。剩下的就是标识符(READER/WRITER)然后是RealmObject
.
的JSON表示
发生的情况是,在成功写入后,reader 读取旧值一段时间,然后读取新值,然后再次读取旧值。如果我重新启动应用程序,那么一切正常,当然,有一段时间了。
//Reader starts reading. The output is correct
[0:] (10:05:44.656) READER:
[{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
[0:] (10:05:54.656) READER:
[{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
[0:] (10:06:04.657) READER:
[{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
//Writer comes in and updates the values.
[0:] (10:06:07.523) WRITER:
{"LastSyncTime":"2016-07-17T22:06:07.521+00:00","LastChangeDate":"2016-07-17T22:11:07.523+00:00","IsManaged":true}
//Reader reads incorrect (OLD) values for a while
[0:] (10:06:14.661) READER: > [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
[0:] (10:06:24.678) READER:
[{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
[0:] (10:06:34.678) READER:
[{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
//Reader suddnely reads correct values
[0:] (10:06:44.678) READER:
[{"LastSyncTime":"2016-07-17T22:06:07.521+00:00","LastChangeDate":"2016-07-17T22:11:07.523+00:00","IsManaged":true}]
//Reader falls back to previous values (????)
[0:] (10:06:54.678) READER:
[{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
[0:] (10:07:04.678) READER:
[{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
代码:
public class TimestampDataObject : RealmObject
{
public DateTimeOffset LastSyncTime { get; set; }
public DateTimeOffset? LastChangeDate { get; set; }
}
写作部分:
Observable.Interval(TimeSpan.FromMinutes(1)).Subscribe(async (v) =>
{
await Realm.GetInstance().WriteAsync(r =>
{
var item = r.All<TimestampDataObject>().AsEnumerable().FirstOrDefault();
if (item == null)
{
item = r.CreateObject<TimestampDataObject>();
}
item.LastSyncTime = DateTimeOffset.UtcNow;
item.LastChangeDate = DateTimeOffset.UtcNow.AddMinutes(5);
Debug.WriteLine($"({DateTime.UtcNow.ToString("hh:mm:ss.fff")}) WRITER: {JsonConvert.SerializeObject(item)}");
});
});
reader:
Observable.Interval(TimeSpan.FromSeconds(10)).Subscribe(v =>
{
var latestTimestampInfo = Realm.GetInstance().All<TimestampDataObject>();
Debug.WriteLine($"({ DateTime.UtcNow.ToString("hh:mm:ss.fff")}) READER: {JsonConvert.SerializeObject(latestTimestampInfo)}");
});
不确定会发生什么。也许 Realm 的一些人可以澄清这一点。
更新
做了一些更多的测试,显然当 reader 和 writer 在同一个 Thread
上时一切正常。在时间戳后立即在日志中添加托管线程 ID。因此,您可以在下面看到线程 11 上的 reader 运行 - 与作者的相同 - 如何正常工作。但是不同线程上的 reader 运行 输出旧值:
//Before the update (correct)
[0:] (10:56:53.679,11) READER:
[{"LastSyncTime":"2016-07-17T22:55:55.573+00:00","LastChangeDate":"2016-07-17T23:00:55.576+00:00","IsManaged":true}]
//The update is done on thread 11
[0:] (10:56:55.552,11) WRITER:
{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}
//Reader on thread 11 outputs correct values
[0:] (10:57:03.702,11) READER:
[{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}]
//Again, the reader is on thread 11. Correct output.
[0:] (10:57:13.702,11) READER:
[{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}]
//A reader on thread 12 outputs incorrect results
[0:] (10:57:23.703,12) READER:
[{"LastSyncTime":"2016-07-17T22:23:19.674+00:00","LastChangeDate":"2016-07-17T22:28:19.676+00:00","IsManaged":true}]
//Again, the reader is on thread 11. Correct output.
[0:] (10:57:33.703,11) READER:
[{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}]
没关系。看来我需要为在没有事件循环的线程上创建和使用的 Realm
个实例调用 Realm.Refresh()
。否则,它们在创建时与写入提交保持同步。
Realm.Refresh()
使领域前进到最新的写入提交并将其用于后续读取。
When you initially open a Realm on a thread, its state will be based
off the most recent successful write commit, and it will remain on
that version until refreshed. Realms are automatically refreshed at
the start of every runloop iteration. If a thread has no runloop
(which is generally the case in a background thread), then
Realm.Refresh() must be called manually in order to advance the
transaction to the most recent state.
reader 部分现在看起来像这样:
Observable.Interval(TimeSpan.FromSeconds(10)).Subscribe(v =>
{
var realm = Realm.GetInstance();
realm.Refresh();
var latestTimestampInfo = realm.All<TimestampDataObject>();
Debug.WriteLine($"({ DateTime.UtcNow.ToString("hh:mm:ss.fff")},{Thread.CurrentThread.ManagedThreadId}) READER: {JsonConvert.SerializeObject(latestTimestampInfo)}");
});
也许我遗漏了一些东西,但我在从 Realm 并行读取和写入同一对象时得到了一些奇怪的结果。
我第一次遇到这个问题是在一个更大的项目中,但现在才设法在测试项目中重现它。
场景:创建一个具有两个 DateTimeOffset
字段的 RealmObject
对象,然后每分钟更新一次。另一个线程每 10 秒读取一次并输出值。
我将首先显示输出,因为它最相关。括号中的时间是记录输出的时间。剩下的就是标识符(READER/WRITER)然后是RealmObject
.
发生的情况是,在成功写入后,reader 读取旧值一段时间,然后读取新值,然后再次读取旧值。如果我重新启动应用程序,那么一切正常,当然,有一段时间了。
//Reader starts reading. The output is correct
[0:] (10:05:44.656) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:05:54.656) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:06:04.657) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
//Writer comes in and updates the values.
[0:] (10:06:07.523) WRITER: {"LastSyncTime":"2016-07-17T22:06:07.521+00:00","LastChangeDate":"2016-07-17T22:11:07.523+00:00","IsManaged":true}
//Reader reads incorrect (OLD) values for a while
[0:] (10:06:14.661) READER: > [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:06:24.678) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:06:34.678) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
//Reader suddnely reads correct values
[0:] (10:06:44.678) READER: [{"LastSyncTime":"2016-07-17T22:06:07.521+00:00","LastChangeDate":"2016-07-17T22:11:07.523+00:00","IsManaged":true}]
//Reader falls back to previous values (????)
[0:] (10:06:54.678) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:07:04.678) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]
代码:
public class TimestampDataObject : RealmObject
{
public DateTimeOffset LastSyncTime { get; set; }
public DateTimeOffset? LastChangeDate { get; set; }
}
写作部分:
Observable.Interval(TimeSpan.FromMinutes(1)).Subscribe(async (v) =>
{
await Realm.GetInstance().WriteAsync(r =>
{
var item = r.All<TimestampDataObject>().AsEnumerable().FirstOrDefault();
if (item == null)
{
item = r.CreateObject<TimestampDataObject>();
}
item.LastSyncTime = DateTimeOffset.UtcNow;
item.LastChangeDate = DateTimeOffset.UtcNow.AddMinutes(5);
Debug.WriteLine($"({DateTime.UtcNow.ToString("hh:mm:ss.fff")}) WRITER: {JsonConvert.SerializeObject(item)}");
});
});
reader:
Observable.Interval(TimeSpan.FromSeconds(10)).Subscribe(v =>
{
var latestTimestampInfo = Realm.GetInstance().All<TimestampDataObject>();
Debug.WriteLine($"({ DateTime.UtcNow.ToString("hh:mm:ss.fff")}) READER: {JsonConvert.SerializeObject(latestTimestampInfo)}");
});
不确定会发生什么。也许 Realm 的一些人可以澄清这一点。
更新
做了一些更多的测试,显然当 reader 和 writer 在同一个 Thread
上时一切正常。在时间戳后立即在日志中添加托管线程 ID。因此,您可以在下面看到线程 11 上的 reader 运行 - 与作者的相同 - 如何正常工作。但是不同线程上的 reader 运行 输出旧值:
//Before the update (correct)
[0:] (10:56:53.679,11) READER: [{"LastSyncTime":"2016-07-17T22:55:55.573+00:00","LastChangeDate":"2016-07-17T23:00:55.576+00:00","IsManaged":true}]
//The update is done on thread 11
[0:] (10:56:55.552,11) WRITER: {"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}
//Reader on thread 11 outputs correct values
[0:] (10:57:03.702,11) READER: [{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}]
//Again, the reader is on thread 11. Correct output.
[0:] (10:57:13.702,11) READER: [{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}]
//A reader on thread 12 outputs incorrect results
[0:] (10:57:23.703,12) READER: [{"LastSyncTime":"2016-07-17T22:23:19.674+00:00","LastChangeDate":"2016-07-17T22:28:19.676+00:00","IsManaged":true}]
//Again, the reader is on thread 11. Correct output.
[0:] (10:57:33.703,11) READER: [{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}]
没关系。看来我需要为在没有事件循环的线程上创建和使用的 Realm
个实例调用 Realm.Refresh()
。否则,它们在创建时与写入提交保持同步。
Realm.Refresh()
使领域前进到最新的写入提交并将其用于后续读取。
When you initially open a Realm on a thread, its state will be based off the most recent successful write commit, and it will remain on that version until refreshed. Realms are automatically refreshed at the start of every runloop iteration. If a thread has no runloop (which is generally the case in a background thread), then Realm.Refresh() must be called manually in order to advance the transaction to the most recent state.
reader 部分现在看起来像这样:
Observable.Interval(TimeSpan.FromSeconds(10)).Subscribe(v =>
{
var realm = Realm.GetInstance();
realm.Refresh();
var latestTimestampInfo = realm.All<TimestampDataObject>();
Debug.WriteLine($"({ DateTime.UtcNow.ToString("hh:mm:ss.fff")},{Thread.CurrentThread.ManagedThreadId}) READER: {JsonConvert.SerializeObject(latestTimestampInfo)}");
});