Mar*_*ark 13 concurrency task swift swiftui
我观看了所有关于 async/await (和演员)的视频,但我仍然有点困惑。
因此,假设我有一个异步方法:func postMessage(_ message: String) async throws并且我有一个简单的 SwiftUI 视图。
@MainActor
struct ContentView: View {
@StateObject private var api = API()
var body: some View {
Button("Post Message") {
Task {
do {
try await api.postMessage("First post!")
} catch {
print(error)
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
在这里,我明确告诉 SwiftUI 使用 ,@MainActor尽管我知道它会从 推断出来@StateObject。
据我了解,由于我们使用该@MainActor工作是在主线程上完成的。这意味着任务上的工作也将在主线程上完成。这不是我想要的,因为上传过程可能需要一些时间。在这种情况下,我可以Task.detached使用不同的线程。这就能解决它。如果我的理解是正确的话。
现在让它变得更复杂一点。如果... postMessage 会返回一个整数形式的帖子标识符,并且我想在视图中呈现它,该怎么办?
struct ContentView: View {
@StateObject private var api = API()
@State private var identifier: Int = 0
var body: some View {
Text("Post Identifier: \(String(describing: identifier))")
Button("Post Message") {
Task {
do {
identifier = try await api.postMessage("First post!")
} catch {
identifier = 0
print(error)
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这将起作用(再次根据我的理解)任务在主线程上运行。如果我现在将其更改为,Task.detached我们将收到错误"Property 'identifier' isolated to global actor 'MainActor' can not be mutated from a non-isolated context"。
这是有道理的,但是我们如何将值返回给主要参与者以便更新视图呢?
也许我的假设是错误的。让我们看看我的 API 类。
actor API {
func postMessage(_ message: String) async throws -> Int {
// Some complex internet code
return 0
}
}
Run Code Online (Sandbox Code Playgroud)
由于 API 在其自己的 actor 中运行。互联网工作也会在不同的线程上运行吗?
Mic*_*nat 20
问题很好,答案很复杂。我在这个主题上花费了大量时间,有关详细信息,请点击评论中的 Apple 开发者论坛链接。
下面提到的所有任务都是非结构化任务,例如。由...制作Task ...
这是关键:“主要参与者是代表主线程的参与者。主要参与者通过主调度队列执行所有同步。
由例如创建的非结构化任务Task.init。Task { ... }继承参与者异步上下文。
分离的任务Task.detached、async let =任务、组任务不继承参与者异步上下文。
示例 1:let task1 = Task { () -> Void in ... }创建并启动新任务,该任务从调用点继承优先级和异步上下文。当在主线程上创建时,任务将在主线程上运行。
示例 2:let task1 = Task.detached { () -> Void in ... }创建并启动不继承优先级或异步上下文的新任务。该任务将在某个线程上运行,很可能在当前线程以外的其他线程上运行。执行者决定。
示例 3:let task1 = Task.detached { @MainActor () -> Void in ... }创建并启动新任务,该任务不继承优先级也不继承异步上下文,但该任务将在主线程上运行,因为它是这样注释的。
很可能,任务将包含至少一个await或async let =命令。这些命令是结构化并发的一部分,您无法影响隐式创建的任务(此处根本不讨论)在哪个线程上执行。Swift 执行者决定这一点。
继承的 Actor 异步上下文与线程无关,每个await线程可能会更改之后,但是 Actor 异步上下文在所有非结构化任务中保持不变(是的,可能位于各个线程上,但这对于执行器来说很重要)。
如果继承的 Actor 异步上下文是 MainActor,则任务从开始到结束都在主线程上运行,因为 Actor 上下文是 MainActor。如果您计划运行一些真正并行的计算,这一点很重要 - 确保所有非结构化任务不在同一线程上运行。
在这两种情况下,ContentView 都在 @MainActor 上:第一种情况是明确的 @MainActor,第二种情况使用 @StateObject 属性包装器,即 @MainActor,因此整个 ContentView 结构是 @MainActor 推断的。https://www.hackingwithswift.com/quick-start/concurrency/understanding-how-global-actor-inference-works
async let = 是一种结构化并发,它不继承 Actor 异步上下文,并按照执行程序的计划立即并行运行(在其他线程上,如果可用)
上面的示例有一个系统缺陷:@StateObject private var api = API()@MainActor。这是由 @StateObject 强制的。因此,我建议将其他 actor 与其他 actor 异步上下文作为依赖项注入,而不使用 @StateObject。async/await 将真正发挥作用,使await 调用保持在正确的actor 上下文中。
你说:
\n\n\n据我了解,由于我们使用该
\n@MainActor工作是在主线程上完成的。意味着工作Task也将在主线程上完成。这不是我想要的,因为上传过程可能需要一些时间。
只是因为上传过程 (a) 可能需要一些时间;(b) 是从主要演员处调用的,这并不在执行请求时主线程将被阻塞。
\n事实上,这就是 Swift concurrency\xe2\x80\x99s 的全部意义await。await当我们从被调用的例程中得到结果时,它会释放当前线程去做其他事情。不要将awaitSwift 并发性(不会阻塞调用线程)与各种并发性混为一谈。wait传统 GCD 模式的各种构造(会阻塞)混为一谈。
例如,考虑从主要参与者启动的以下代码:
\nTask {\n do {\n identifier = try await api.postMessage("First post!")\n } catch {\n identifier = 0\n print(error)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\nTask是的,因为您从主要参与者使用并调用了它,所以该代码也将在主要参与者上运行。因此,identifier将更新主要演员。但它没有说明正在postMessage使用哪个参与者/队列/线程。该关键字的意思是 \xe2\x80\x9cI\' 将暂停这条执行路径,并让主线程在我们发起其网络请求和最终响应await时去做其他事情。\xe2\x80\x9dawaitpostMessage
你问:
\n\n\n让我们看看我的 API 类。
\nRun Code Online (Sandbox Code Playgroud)\nactor API {\n func postMessage(_ message: String) async throws -> Int {\n // Some complex internet code\n\n return 0\n }\n}\n由于 API 在其自己的 actor 中运行。互联网是否也可以在不同的线程上运行?
\n
作为一般规则,网络库异步执行其请求,不会阻塞调用它们的线程。假设 \xe2\x80\x9ccomplex 互联网代码\xe2\x80\x9d 遵循此标准约定,那么您就不必担心它在哪个线程上运行。请求运行时,actor\xe2\x80\x99s 线程不会被阻塞。
\n更进一步,事实是它API自己的演员对于这个问题来说并不重要。即使API在主要参与者上,由于网络请求是异步运行的,因此无论使用什么线程都API不会被阻塞。
(注意:上面假设 \xe2\x80\x9c 复杂的互联网代码 \xe2\x80\x9d 遵循传统的异步网络编程模式。如果没有看到此代码的代表性示例,我们显然无法进一步评论。)
\n如果您对 Swift 并发如何为我们管理线程感兴趣,请观看 WWDC 2021 视频Swift 并发:幕后花絮。
\n