从 withCheckedContinuation 恢复后运行代码

问题描述 投票:0回答:1

我想知道在检查的延续中的

resume
之后运行代码的影响,但我找不到任何资源。示例:

func doSomething(value: Bool) {
    print(value)
}

func myAsyncMethod() async -> Bool {
    withCheckedContinuation { continuation in 
        continuation.resume(returning: true)
        doSomething() // <---
    }
}

我知道这不是最好的方法,但我试图理解它的问题(如果有的话)。大家有什么想法吗?

swift concurrency
1个回答
0
投票

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) { … }

有几个问题:

  1. 您无法保证

    doSomething
    doSomethingElse
    的执行顺序,更不用说它们是否可以并行运行。

  2. 您无法知道

    doSomething
    何时完成。事实上,在极端情况下,
    myAsyncMethod
    实际上可能会在
    doSomething
    完成之前返回。

  3. 您无法取消

    doSomething

  4. Swift 并发的“协作线程池”会假设它可以创建多少个线程(仅限于设备上的处理器数量,以避免过度使用 CPU),并在

    doSomething
    继续时占用一个线程在满足
    await
    之后执行,这意味着Swift并发的假设可能不成立。

    有关更多信息,请参阅 WWDC 2021 视频 Swift 并发:幕后故事 :它没有具体讨论这个问题,但它提供了有关 Swift 并发线程模型、它强加给开发人员的契约等方面的见解。

  5. 在您的示例中,您没有在

    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
完成之前返回,这是预期的行为,有比赛等:

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.