我无法找到讨论任务是否同时运行的文档。或者,如果任务按顺序运行,则在某个不可见的队列中。
以下是我的应用程序遇到的一个精简问题,它可以在游乐场中运行,这引发了这个问题。
import UIKit
import Foundation
import Combine
struct Info {
var id: String
var value: Int
}
class DataStore {
// pretend this is storing into core data
func store(info: Info, id: String) {
print(" store \(info)")
let start = CACurrentMediaTime()
while CACurrentMediaTime() - start < 2 { }
}
}
let dataStore = DataStore()
let subj = PassthroughSubject<Info, Never>()
let cancel = subj.sink { info in
print("Start task for \(info)")
// is there a way to queue tasks so that we
Task {
print(" start \(info)")
dataStore.store(info: info, id: info.id)
print(" finish: \(info)")
}
}
subj.send(Info(id: "A", value: 1))
subj.send(Info(id: "A", value: 2))
subj.send(Info(id: "A", value: 3))
subj.send(Info(id: "A", value: 4))
let queueA = DispatchQueue(label: "A", attributes: .concurrent)
let queueB = DispatchQueue(label: "B", attributes: .concurrent)
queueA.async {
subj.send(Info(id: "A", value: 1))
subj.send(Info(id: "A", value: 2))
subj.send(Info(id: "A", value: 3))
subj.send(Info(id: "A", value: 4))
}
queueB.async {
subj.send(Info(id: "B", value: 1))
subj.send(Info(id: "B", value: 2))
subj.send(Info(id: "B", value: 3))
subj.send(Info(id: "B", value: 4))
}
queueA.async {
subj.send(Info(id: "A", value: 1))
subj.send(Info(id: "A", value: 2))
subj.send(Info(id: "A", value: 3))
subj.send(Info(id: "A", value: 4))
}
queueB.async {
subj.send(Info(id: "B", value: 1))
subj.send(Info(id: "B", value: 2))
subj.send(Info(id: "B", value: 3))
subj.send(Info(id: "B", value: 4))
}
// Note that a closure is not started until the other one has finished
请注意,在前一个闭包完成之前,闭包永远不会开始。现在我不知道这是因为传递主题是让事情按顺序进行还是与发布者有其他关系。
我知道这不是一个完美的例子,因为发布者的原因,但我的应用程序有旧的组合代码与新的异步等待代码接口。
附注如果我使用异步序列而不是发布者,会有什么不同吗?
您询问异步等待任务是否按顺序运行。答案是“视情况而定”。
Task
“[代表当前参与者异步运行]给定的...操作,作为新的顶级任务的一部分。”正如《Swift 编程语言:并发》中所说,“参与者一次只允许一个任务访问其可变状态”。因此,简而言之,如果您的异步等待任务位于 Actor 上,则可以阻止并行执行。
为了说明这一点,我将在 Instruments 中分析该应用程序(使用 os_signpost
事件)并使任务执行速度稍慢一些(以便我们可以轻松查看发生了什么)。
无论如何,请考虑这个 SwiftUI 示例,其中Task
不与任何特定参与者隔离:
import SwiftUI
import os.log
let poi = OSSignposter(subsystem: "Test", category: .pointsOfInterest)
struct ContentView: View {
var body: some View {
VStack {
Button("Launch Ten Tasks") {
launchTenTasks()
}
}
.padding()
}
func launchTenTasks() {
let experiment = Experiment()
for i in 0 ..< 10 {
Task {
experiment.spin(index: i, for: .seconds(1))
}
}
}
}
class Experiment {
func spin(index: Int, for duration: Duration) {
let id = poi.makeSignpostID()
poi.withIntervalSignpost(#function, id: id, "\(index)") {
let start = ContinuousClock().now
while start.duration(to: .now) < duration { } // blocking thread is bad idea, but just simulating some slow, synchronous task
}
}
}
结果:
现在这有点例外。这只是证明单独使用
Task
不一定足以避免并行执行。
但我们经常将任务与演员结合使用。在这种情况下,您do实现了非并行执行。 因此,如果我将
Experiment
设为 actor,则这些方法将是 actor 隔离的,因此不会并行运行。现在,可以使用
@MainActor
限定符让它在主 actor 上运行(这在本例中会很糟糕,因为它会阻塞主线程),或者如下所示,将其设为一个单独的 actor:actor Experiment {
func spin(index: Int, for interval: TimeInterval) {
...
}
}
产量:队列。请注意,第 8 项和第 9 项没有顺序。 (它们主要按顺序处理,但并非严格如此。)如果您想要类似队列的行为,其中项目以严格的 FIFO 方式处理,您可以使用
AsyncSequence
,例如
AsyncStream
或AsyncChannel
。但这可能超出了这个问题的范围。
在你的例子中,如果你的DataStore
store
不应该被标记为
async
方法,因为它不执行任何异步操作(至少在这一点上)。
关于您的示例的其他一些注意事项:您的任务是如此之快,以至于很难看出它是否真的是顺序运行还是并行运行:请注意,我使用人为较慢的示例执行测试,以准确地清楚地表明并行执行与非并发执行。
因此,在真实的应用程序中运行它,如果您想确保它不会并行运行,请确保您的方法与参与者隔离。