Swift并发编程中Realm数据库的线程安全实践指南

Viewed 0

Swift的并发系统为异步和并行编程提供了内置的结构化支持。在Swift应用中使用Realm数据库时,需要特别注意其线程模型与Swift并发线程行为之间的兼容性。从Realm Swift SDK 10.39.0版本开始,引入了对Swift Actor的原生支持,允许开发者在单个actor隔离或跨actor的上下文中使用Realm,这大大简化了在MainActor和后台actor中管理Realm的过程,并更新了此前许多关于并发的建议。

在Swift中,await关键字标记了代码执行中可能的暂停点。需要注意的是,从Swift 5.7开始,一旦代码在await处暂停,后续的执行可能会在不同的线程上恢复。这意味着,在await调用之后,操作Realm的代码可能不再运行于原始的线程上。

这一行为与Realm的“活动对象”范式存在根本性冲突。在Realm中,活动对象、集合以及Realm实例本身都是线程限制的——它们仅在其被创建的线程上有效,因此不能直接跨线程传递。不过,Realm提供了如冻结对象ThreadSafeReference等机制来安全地跨线程共享数据,但这些机制通常需要开发者进行显式的处理。

许多Realm Swift API已经与Swift的async/await语法兼容,例如异步登录、管理电子邮件/密码用户、关联用户身份、打开同步或本地Realm、等待来自其他actor的通知、管理灵活同步订阅、异步调用无服务器函数以及执行远程MongoDB查询等。

对于常见的后台写入场景,Realm提供了两个专门的异步写入API:writeAsync()asyncWrite()writeAsync() API允许使用完成处理程序来执行写入,它将获取写锁和提交事务的过程移至后台,而写入逻辑块本身仍在调用线程上运行,从而确保了线程安全。asyncWrite() API则直接与Swift的async/await语法集成,它在等待写入时暂停调用任务而非阻塞线程,实际的磁盘I/O由后台工作线程完成,对于小型写入,这可能减少主线程的阻塞时间。

当使用Swift的Task和TaskGroup来管理并发工作时,需要警惕其中的await暂停点可能导致线程切换。如果Task中包含访问Realm的代码,为了确保线程安全,必须使用@MainActor来标记相关函数,以保证所有Realm操作都在主线程上执行,但这可能会部分牺牲Task带来的并发优势。

在考虑将Realm与Swift Actor结合使用时,actor隔离看似一个安全的方案。然而,在非@MainActor的异步函数中使用Realm目前仍不受支持。在Swift 5.7中,未隔离的异步函数会在后台线程上运行,这容易引发“从错误线程访问Realm”的崩溃。为了避免线程问题,可以采取以下措施:升级到支持actor隔离的Realm版本;确保访问Realm的异步函数使用@MainActor标记,以固定执行上下文;或者使用writeAsync(同步调用)或asyncWrite(异步调用)API来安全地执行后台写入。

Realm Swift SDK中的公共API类型大致可分为三类:

  • 符合Sendable协议的类型:例如AnyBSONAnyRealmCollectionListMapMutableSet等,这些类型可以在线程间安全传递。
  • 非Sendable且无线程限制的类型:例如RLMAppConfigurationRLMFindOptions等,它们可以在线程间共享,但访问时需要额外的同步措施。
  • 线程限制的类型:例如RLMAppRLMAsyncOpenTaskRLMRealmRLMResults等,除非被冻结,否则它们严格限制在特定的隔离上下文(如创建它们的线程或actor)中使用,无法直接跨上下文传递。

理解这些分类对于编写正确、高效的并发Realm代码至关重要。

0 Answers