Adi*_*ain 1 swift swiftui swift-concurrency
我有一个视图模型声明如下:
@MainActor class ContentViewModel: ObservableObject {
...
}
Run Code Online (Sandbox Code Playgroud)
我想向该模型添加一个函数,该函数在MainActor上下文内部和外部混合执行工作。
我进行了一些实验,并提出了两个实现此目的的选项,如下所述。
在视图模型中将新函数标记为nonisolated并在该函数内启动一个任务,如下所示:
nonisolated func someFunction() {
Task {
// Do work here.
// Use 'await' to make calls to the 'MainActor' context
// and to make other asynchronous calls.
}
}
Run Code Online (Sandbox Code Playgroud)
在视图中调用该函数如下:
Button("Execute") {
viewModel.someFunction()
}
Run Code Online (Sandbox Code Playgroud)
在视图模型中将新函数标记为async并省略任务启动,如下所示:
func someFunction() async {
// Do work here.
// No need to 'await' to make calls to the 'MainActor' context
// given that this function runs in the 'MainActor' context.
// Use 'await' to make asynchronous calls
// and to free up the 'MainActor' whilst those calls execute.
}
Run Code Online (Sandbox Code Playgroud)
在视图中调用该函数如下:
Button("Execute") {
Task {
await viewModel.someFunction()
}
}
Run Code Online (Sandbox Code Playgroud)
上述两个选项中的任何一个是否比另一个选项具有优势,或者它们在功能上是否相同?是否有一个替代选项对于 Swift 和 SwiftUI 来说更惯用,并且比上述两个选项更具优势?
当我最初问这个问题时,我的印象是我必须使用上面的选项 2来await调用上下文。这是不正确的。MainActorsomeFunction()
我已经编辑了选项 2 中代码块中的注释,以删除这个不正确的假设,这样我就不会误导将来偶然发现这个问题的任何人。
为了回答有关如何从当前演员身上消除这种情况的问题,有多种方法:
\nactor\xe2\x80\xa6 中,如果存在某些共享的可变状态或需要防止多个调用导致并行执行,则这是更好的选择,但如果目的只是从主要参与者中获取单个函数,则可能有点过分了;detached任务 \xe2\x80\xa6 这是一种让当前参与者工作的简单方法,但需要非结构化并发,给作者带来手动取消处理的负担等;或者async函数,如SE-0338 \xe2\x80\xa6 中所述,这使我们处于结构化并发领域及其带来的好处,同时使我们摆脱当前的参与者。这就引出了一个问题:你是否需要摆脱someFunction主角。事实上,该函数随后将 \xe2\x80\x9cmake 调用MainActor\xe2\x80\x9d,这表明您也应该将此函数保留在主要参与者上。
someFunction因此,要确保永远不会阻塞主要参与者,需要考虑一些注意事项:
如果这段代码只是await其他async方法,那么就不用担心阻塞主线程/参与者。一旦任务遇到await挂起点,当前任务就会被挂起,但当前参与者(即主要参与者)会在异步代码运行时被释放以执行其他任务。当前线程/参与者不会被阻塞。
例如,请考虑以下情况:
\nfunc someFunction() async {\n let response = await someThingElse() // this is asynchronous and will not block\n self.objects = results.objects // now update property isolated to the main actor with no `await`\n}\nRun Code Online (Sandbox Code Playgroud)\n如果你采用await其他async方法,现在someFunction是否与主要演员隔离已经变得无关紧要了。一旦someFunction遇到await暂停点,当前的 Actor 就会被释放来执行其他任务,同时异步工作正在进行中。正如你所看到的,如果someFunction稍后需要对主要参与者执行某些操作,这会使其意图变得清晰并简化您的代码。
如果someFunction在继续对主要参与者执行更多操作之前执行任何缓慢且同步的工作(例如,文件 i/o 等),则将该同步工作(并且仅该工作)移至其自己的函数中。您可以从主要参与者中获取这个新函数(例如,非隔离async函数、独立任务、其自己的参与者等等)。但是,一旦带有同步代码的这个新函数离开了主要参与者,您就可以继续someFunction在主要参与者上await调用这个新函数。这简化了someFunction,使其意图清晰(它将在某个时候使用主要演员),但让主要演员摆脱缓慢而同步的工作。
同样,通过这种模式,主要参与者将不会被阻止,并且只有缓慢的同步代码才会从当前参与者中移出。
\n顺便说一句,我会避免不必要地引入非结构化并发Task {\xe2\x80\xa6}。对于非结构化并发,您承担处理任务取消的全部责任。例如,请注意,如果您像选项 1 中那样丢弃生成的任务引用,则 \xe2\x80\x9c 会使您无法显式取消该任务。\xe2\x80\x9d 正确处理取消Task {\xe2\x80\xa6}工作; 如果您保持结构化并发,您就可以免费获得这些行为。如果可以的话,避免非结构化并发。
我还建议您也避免引入分离任务,除非绝对必要,因为这也会引入非结构化并发。如文档所示所说:
\n\n\n如果可以使用结构化并发功能(如子任务)对操作进行建模,则不要使用分离任务。子任务继承父任务\xe2\x80\x99的优先级和任务本地存储,取消父任务会自动取消其所有子任务。您需要使用分离任务手动处理这些注意事项。
\n
| 归档时间: |
|
| 查看次数: |
916 次 |
| 最近记录: |