我想知道在检查的延续中的
resume
之后运行代码的影响,但我找不到任何资源。示例:
func doSomething(value: Bool) {
print(value)
}
func myAsyncMethod() async -> Bool {
withCheckedContinuation { continuation in
continuation.resume(returning: true)
doSomething() // <---
}
}
我知道这不是最好的方法,但我试图理解它的问题(如果有的话)。大家有什么想法吗?
tl;博士
您的特定示例在技术上没有任何无效之处,但是在满足
await
后继续执行附加代码的这种(反)模式会暴露出某些风险、限制等,如下所述。
主要担心的是,这违反了 Swift 并发的核心原则:当您
await
时,当前执行上下文将暂停,直到 async
工作完成。我们的预期是,当 await
满足并且调用者函数中恢复执行时,我们之前 await
完成的工作就完成了,但是让 doSomething
继续运行违反了这个假设。
考虑:
func myAsyncMethod() async -> T {
let result = await withCheckedContinuation { continuation in
someLegacyFunction { (value: T) in
continuation.resume(returning: value)
doSomething(with: value)
}
}
doSomethingElse(with: result)
return result
}
func doSomething(with value: T) { … }
func doSomethingElse(with value: T) { … }
有几个问题:
您无法保证
doSomething
和 doSomethingElse
的执行顺序,更不用说它们是否可以并行运行。
您无法知道
doSomething
何时完成。事实上,在极端情况下,myAsyncMethod
实际上可能会在 doSomething
完成之前返回。
您无法取消
doSomething
。
Swift 并发的“协作线程池”会假设它可以创建多少个线程(仅限于设备上的处理器数量,以避免过度使用 CPU),并在
doSomething
继续时占用一个线程在满足await
之后执行,这意味着Swift并发的假设可能不成立。
有关更多信息,请参阅 WWDC 2021 视频 Swift 并发:幕后故事 :它没有具体讨论这个问题,但它提供了有关 Swift 并发线程模型、它强加给开发人员的契约等方面的见解。
在您的示例中,您没有在
doSomething
中执行任何实质性操作,但未来的开发人员很容易在 doSomething
中添加代码,这可能会带来问题。
总而言之,这是一种反模式,它不必要地让您面临某些风险,而只需将
resume(returning:)
作为等待中的 withCheckedContinuation
所做的最后一件事就可以轻松避免这些风险。在您的特定示例中,不太可能出现任何严重问题,但很容易构建导致非常不寻常行为的示例。
例如,考虑以下内容,其中
a
在延续中包装基于遗留完成处理程序的方法 b
,但在恢复后调用 c
after;并且 a
在检查的延续的 d
之后调用 await
:
class Experiment {
let poi = OSSignposter(subsystem: "Experiment", category: .pointsOfInterest)
func a() async -> Int {
let state = poi.beginInterval(#function, id: poi.makeSignpostID())
let result = await withCheckedContinuation { continuation in
b { (value: Int) in
continuation.resume(returning: value) // warning: should really do this *after* calling `c`
self.c(with: value)
}
}
defer { poi.endInterval(#function, state, "\(result)") }
d(with: result)
return result
}
func b(completion: @escaping (Int) -> Void) {
let state = self.poi.beginInterval(#function, id: self.poi.makeSignpostID())
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
completion(42)
self.poi.endInterval(#function, state, "\(42)")
}
}
func c(with value: Int) {
poi.withIntervalSignpost(#function, id: poi.makeSignpostID(), "\(value)") {
Thread.sleep(forTimeInterval: 2) // we would generally never Thread.sleep`, but it is here for illustrative purposes; usually we would `Task.sleep`, which is non-blocking, but your example was using synchronous methods, so I am giving synchronous example
}
}
func d(with value: Int) {
poi.withIntervalSignpost(#function, id: poi.makeSignpostID(), "\(value)") {
Thread.sleep(forTimeInterval: 1)
}
}
}
当我使用 Instruments 进行分析时,会导致
a
在 b
或 c
完成之前返回,这是预期的行为,有比赛等: