Task内部的回调会在主线程自动调用

Mal*_*loc 0 concurrency task async-await swift

曾几何时,在 Async/Await 出现之前,我们使用 URLSession dataTask 向服务器发出简单的请求。回调不会在主线程上自动调用,我们必须手动分派到主线程才能执行一些 UI 工作。例子:

DispatchQueue.main.async {
    // UI work
}
Run Code Online (Sandbox Code Playgroud)

忽略这一点将导致应用程序崩溃,因为我们尝试在与主队列不同的队列上更新 UI。

现在有了 Async/Await ,事情变得更容易了。我们仍然必须使用 调度到主队列MainActor

await MainActor.run {
 // UI work
}
Run Code Online (Sandbox Code Playgroud)

奇怪的是,即使我不使用MainActor任务中的代码似乎也在主线程上运行,并且更新 UI 似乎是安全的。

Task {
       let api = API(apiConfig: apiConfig)
        
       do {
              let posts = try await api.getPosts() // Checked this and the code of getPosts is running on another thread.
              self.posts = posts 
              self.tableView.reloadData()
              print(Thread.current.description)
          } catch {
              // Handle error
          }
}
Run Code Online (Sandbox Code Playgroud)

我预计我的代码会导致崩溃,因为理论上我试图更新表视图而不是从主线程,但日志显示我位于主线程上。日志print如下:

<_NSMainThread: 0x600003bb02c0>{number = 1, name = main}
Run Code Online (Sandbox Code Playgroud)

这是否意味着在执行 UI 操作之前无需检查我们所在的队列?

Rob*_*Rob 5

关于Task {\xe2\x80\xa6},这将 \xe2\x80\x9c 创建一个在当前 actor\xe2\x80\x9d 上运行的非结构化任务(请参阅Swift 并发:非结构化并发)。这是从同步上下文启动异步任务的好方法。而且,如果由主要演员调用,这个Task也将由主要演员负责。

\n

在您的情况下,我会将模型更新和 UI 刷新移至标记为在主要参与者上运行的函数:

\n
@MainActor\nfunc update(with posts: [Post]) async {\n    self.posts = posts \n    tableView.reloadData()\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后你可以这样做:

\n
Task {\n    let api = API(apiConfig: apiConfig)\n            \n    do {\n        let posts = try await api.getPosts() // Checked this and the code of getPosts is running on another thread.\n        self.update(with: posts)\n    } catch {\n        // Handle error\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

它的美妙之处在于,如果你\xe2\x80\x99还没有成为主角,编译器会告诉你必须使用awaitupdate方法。编译器会告诉你是否需要await

\n

如果您还没有\xe2\x80\x99 看过它,我可能建议观看 WWDC 2021 视频Swift 并发:更新示例应用程序。它提供了许多有关将代码转换为 Swift 并发的实用技巧,但特别是在 24:16 ,他们介绍了从DispatchQueue.main.async {\xe2\x80\xa6}Swift 并发的演变(例如,最初建议直观的MainActor.run {\xe2\x80\xa6}步骤,但在接下来的几分钟内,说明为什么即使这样做也是不必要的) ,但也讨论了您可能想要使用此功能的罕见情况)。

\n
\n

顺便说一句,在 Swift 并发中,光看Thread.current是不可靠的。因此,这种做法可能会在未来的编译器版本中被禁止。

\n

如果您观看 WWDC 2021 Swift 并发:幕后花絮,您将了解支撑 Swift 并发的各种机制,并且您将更好地理解为什么查看Thread.current可能会导致各种不正确的结论。

\n