Swift 并发 - Swift SDK
在此页面上
Swift 的并发系统为以结构化方式编写异步和并行代码提供了内置支持。有关 Swift 并发系统的详细概述,请参阅 Swift 编程语言并发主题。
虽然本页的注意事项广泛适用于使用具有 Swift 并发功能的 Realm,但 Realm Swift SDK 版本 10.39.0 添加了新的支持,可结合使用 Realm 与 Swift Actor。您可以使用与单个 actor 隔离的 Realm,也可以跨 actor 使用 Realm。
Realm 的 actor 支持简化了在 MainActor 和后台 actor 上下文中使用 Realm 的过程,并取代了本页上有关并发注意事项的大部分建议。有关更多信息,请参阅结合使用 Realm 与 Actor - Swift SDK。
Realm 并发注意事项
当您在应用中实现并发功能时,请考虑以下有关 Realm 线程模型和 Swift 并发线程行为的注意事项。
使用 Await 暂停执行
在任何位置使用 Swift 关键字 await
均用于标志代码执行中可能的暂停点。在 Swift 5.7 中,一旦代码暂停,后续代码可能无法在同一线程上执行。这意味着,只要您在代码中使用 await
,后续代码就可以在与其之前代码或之后代码不同的线程上执行。
这本质上与 Realm 的活动对象范例不兼容。活动对象、集合和 Realm 实例是线程限制的:也就是说,它们仅在创建它们的线程上有效。实际上,这意味着您不能将活动实例传递给其他线程。但是,Realm 提供了几种跨线程共享对象的机制。这些机制通常要求您的代码进行一些显式处理,以安全地跨线程传递数据。
你可以使用其中一些机制(例如冻结对象或 ThreadSafeReference ),通过 await
关键字跨线程安全地使用 Realm 对象和实例。你还可以使用 @MainActor
标记任何异步 Realm 代码来避免与线程相关的问题,以确保应用始终在主线程上执行此代码。
最基本的规则的是,请记住,在 await
上下文中使用 Realm 而不包含线程保护可能会产生不一致的行为。有时,代码可能会成功。在其他情况下,代码可能会抛出与在不正确的线程上写入相关的错误。
异步/等待 API
许多涉及使用 Atlas App Services 应用或同步 Realm 的 Realm Swift API 都与 Swift 的 async/await 语法兼容。例如,请查看:
如果您有与 Swift 异步/等待 API 相关的特定功能请求,请参阅 Realm 的 MongoDB 反馈引擎。Realm Swift SDK 团队计划根据社区反馈和 Swift 并发演进继续开发并发相关功能。
执行后台写入
异步代码的一个常见用例是在不阻塞主线程的情况下在后台执行写入操作。
Realm 提供了两个可允许执行异步写入的 API:
writeAsync() API 允许使用 Swift 完成处理程序执行异步写入。
asyncWrite() API 允许使用 Swift async/await 语法执行异步写入。
这两个 API 都允许您在后台添加、更新或删除对象,而无需使用冻结对象或传递线程安全的引用。
使用 writeAsync()
API 时,等待获取写锁和提交事务是在后台进行的。写入块本身在调用线程上运行。这将提供线程安全性,无需您手动处理冻结对象或跨线程传递引用。
然而,当执行写入块本身时,这确实会阻止调用线程上的新事务。这意味着使用 writeAsync()
API 进行的大型写入可能会在执行时阻止小型快速写入。
在等待自己的写入轮次时,asyncWrite()
API会暂停调用任务,而不是阻塞线程。此外,将数据写入磁盘的实际 I/O 由后台工作线程完成。对于小型写入,在主线程上使用此函数造成的主线程阻塞时间可能会更短(与手动将写入分派到后台线程相比)。
有关包括代码示例在内的更多信息,请参阅:执行后台写入。
任务和任务组
Swift 并发提供 API 来管理 任务 和 任务组 。Swift 并发文档 将任务定义为可以作为程序的一部分异步运行的工作单元。任务允许您具体定义异步工作单元。 任务组允许您定义任务集合,作为父任务组下的一个单元执行。
任务和任务组可支持让线程去处理其他重要工作或取消可能会阻碍其他操作的长时间运行的任务。为了获得这些好处,您可能希望使用任务和任务组在后台管理 Realm 写入。
但是,上面使用 Await 暂停执行中描述的线程限制约束应用于任务上下文。 如果您的任务包含await
点,则后续代码可能会在不同的线程上运行或恢复,并违反 Realm 的线程限制。
您必须使用 @MainActor
对在任务上下文中运行的函数进行注释,以确保访问 Realm 的代码仅在主线程上运行。这抵消了使用任务的一些好处,并且可能意味着这对于使用 Realm 的应用程序来说不是一个好的设计选择,除非您仅将任务用于管理用户等网络活动。
Actor 隔离
提示
另请参阅:结合使用 Realm 与 Swift Actor
本部分中的信息适用于 Realm SDK 10.39.0 之前的版本。从 Realm Swift SDK 版本 10.39.0 及更高版本开始,SDK 支持将 Realm 与 Swift Actor 和相关异步功能结合使用。
有关更多信息,请参阅结合使用 Realm 与 Actor - Swift SDK。
Actor 隔离让人感觉就像是将 Realm 访问限制为专用 Actor,因此似乎是在异步上下文中管理 Realm 访问的安全方法。
不过,目前还不支持在非 @MainActor
异步函数中使用 Realm。
在 Swift 5.6 中,这通常是巧合。无论等待的任务在哪个线程上运行,await
之后的执行都会继续。在异步函数中使用 await Realm()
会导致其后的代码在主线程上运行,直到您下次调用与 actor 隔离的函数为止。
每当更改 actor 隔离上下文时,Swift 5.7 就会跳转线程。未隔离的异步函数始终在后台线程上运行。
如果你有使用 await Realm()
且在 5.6 版本中正常运行的代码,则将该函数标记为 @MainActor
将使其可以在 Swift 5.7 中正常运行。它将像在 5.6 版本中一样以非有意的方式运行。
与并发代码相关的错误
大多数情况下,您看到的与通过并发代码访问 Realm 相关的错误都是 Realm accessed from incorrect thread.
,这是由于本页所述的线程隔离问题造成的。
为了在使用 Swift 并发功能的代码中避免出现与线程相关的问题,请执行以下操作:
升级到支持 actor 隔离的 Realm 的 Realm Swift SDK 版本,并使用该版本作为手动管理线程的替代方法。有关更多信息,请参阅结合使用 Realm 与 Actor - Swift SDK。
请勿在访问 Realm 时更改执行上下文。如果您在主线程上打开一个 Realm 来为用户界面提供数据,请使用
@MainActor
注释异步访问该 Realm 的后续函数,以确保它始终在主线程上运行。请记住,await
标记了一个可以更改为其他线程的暂停点。不使用 actor 隔离 Realm 的应用程序可以使用
writeAsync
API 来执行后台写入。这以线程安全的方式管理 Realm 访问,而不需要您自己编写专门的代码来实现。这是一个特殊的 API,它将写入过程的各个方面外包(在安全的情况下)以在异步上下文中运行。除非您要写入 actor 隔离的 Realm,否则请勿将此方法与 Swift 的async/await
语法结合使用。在代码中同步使用此方法。或者,在等待写入异步 Realm 时,您可以将asyncWrite
API 与 Swift 的async/await
语法结合使用。如果您想要显式编写非 actor 隔离的并发代码(即以线程安全的方式访问 Realm),您可以适时跨线程显式传递实例,以避免与线程相关的崩溃。这需要深入理解 Realm 的线程模型,并注意 Swift 并发线程行为。
符合 Sendable 协议、不符合 Sendable 协议和具有线程限制的类型
Realm Swift SDK 公共 API 中包含的类型分为以下三大类:
符合 Sendable 协议
不符合 Sendable 协议且不具有线程限制
线程限制
您可以在线程之间共享不符合 Sendable 协议且不受线程限制的类型,但必须对它们进行同步。
除非被冻结,否则具有线程限制的类型仅限于隔离上下文。即使使用同步,也无法在这些上下文之间传递它们。
符合 Sendable 协议 | Non-Sendable | 线程限制 |
---|---|---|
AnyBSON | RLMAppConfiguration | AnyRealmCollection |
AsyncOpen | RLMFindOneAndModifyOptions | AnyRealmValue |
AsyncOpenSubscription | RLMFindOptions | 名单 |
RLMAPIKeyAuth | RLMNetworkTransport | Map |
RLMApp | RLMRequest | MutableSet |
RLMAsyncOpenTask | RLMResponse | 投射 |
RLMChangeStream | RLMSyncConfiguration | RLMArray |
RLMCompensatingWriteInfo | RLMSyncTimeoutOptions | RLMChangeStream |
RLMCredentials | RLMDictionary | |
RLMDecimal128 | RLMDictionaryChange | |
RLMEmailPasswordAuth | RLMEmbeddedObject | |
RLMMaxKey | RLMLinkingObjects | |
RLMMinKey | RLMObject | |
RLMMongoClient | RLMPropertyChange | |
RLMMongoCollection | RLMRealm | |
RLMMongoDatabase | RLMResults | |
RLMObjectId | RLMSection | |
RLMObjectSchema | RLMSectionedResults | |
RLMProgressNotification | RLMSectionedResultsChangeset | |
RLMProgressNotificationToken | RLMSet | |
RLMProperty | RLMSyncSubscription | |
RLMPropertyDescriptor | RLMSyncSubscriptionSet | |
RLMProviderClient | RealmOptional | |
RLMPushClient | RealmProperty | |
RLMSchema | ||
RLMSortDescriptor | ||
RLMSyncErrorActionToken | ||
RLMSyncManager | ||
RLMSyncSession | ||
RLMThreadSafeReference | ||
RLMUpdateResult | ||
RLMUser | ||
RLMUserAPIKey | ||
RLMUserIdentity | ||
RLMUserProfile | ||
ThreadSafe |