首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >阻止对异步函数的调用

阻止对异步函数的调用
EN

Code Review用户
提问于 2022-01-13 16:37:40
回答 2查看 515关注 0票数 2

在迁移到结构化并发时,有时临时对异步函数进行阻塞调用可能很方便--例如:

代码语言:javascript
复制
let x: Int = try blocking {
    try await Task.sleep(nanoseconds: 1)
    return 5
}

print(x)

我现在已经这样做了,但我并不喜欢它:

代码语言:javascript
复制
@available(*, deprecated, message: "For use only as a last resort or to allow incremental migration to structured concurrency")
public func blocking<A>(_ ƒ: @escaping () async -> A) -> A {
    let semaphore = DispatchSemaphore(value: 0)
    let got = Got<A>()
    Task(priority: .high) {
        got.a = await ƒ()
        semaphore.signal()
    }
    semaphore.wait()
    return got.a!
}

还有一个投掷变体:

代码语言:javascript
复制
@available(*, deprecated, message: "For use only as a last resort or to allow incremental migration to structured concurrency")
public func blocking<A>(_ ƒ: @escaping () async throws -> A) throws -> A {
    let semaphore = DispatchSemaphore(value: 0)
    let got = Got<Result<A, Error>>()
    Task(priority: .high) {
        do { got.a = try await .success(ƒ()) }
        catch { got.a = .failure(error) }
        semaphore.signal()
    }
    semaphore.wait()
    return try got.a!.get()
}

其中,Got只是一个简单的类,用于保存对结果的引用:

代码语言:javascript
复制
private class Got<A> {
    var a: A?
}

在回顾了标准库的并发模块之后,我现在认为没有一种惯用的表达方式:“我可以在等待这个特定的异步调用时阻止我的同步函数”。

EN

回答 2

Code Review用户

发布于 2022-01-14 09:03:45

我知道您认为这只是迁移过程中的一个临时步骤,但我完全不鼓励使用这种模式。在Swift并发中使用信号量是不安全的。

在WWDC 2021视频快速并发:幕后中,苹果讨论了“保存运行时合同”和确保“向前进展”的必要性,并正式禁止使用信号量:

另一方面,像信号量和条件变量这样的原语在Swift并发中使用是不安全的。这是因为它们向Swift运行时隐藏依赖信息,但在代码中引入了执行中的依赖项。

这是一个很有启发性的视频,我可能会建议完整地观看它。

关于是否存在用现有API实现这一目标的“技巧”,我担心答案是,最好是简单地重构代码,这样就不需要这种模式了。如果您有一些倾向于使用上述代码的代码,我可能建议将其作为一个单独的问题发布,并且我们可以帮助您解决这个问题,而无需使用信号量。

票数 2
EN

Code Review用户

发布于 2022-01-14 12:32:47

建议的方法同时挂在iOS和macOS上(只是不是在并行执行的单元测试中),这本身就很吸引人。见内联注释:

代码语言:javascript
复制
@main
struct BlockerApp: App {
    
    @State var count = 0
    
    var body: some Scene {
        WindowGroup {
            VStack {
                
                Text("\(count)")
                
                Button("Increment") { // tapping on the button hangs the app
                    count += blocking { 1 } // the closure is never called
                }
            }
        }
    }
}

func blocking<A>(_ ƒ: @escaping () async -> A) -> A {
    let semafore = DispatchSemaphore(value: 0)
    let got = Got<A>()
    Task.detached(priority: .high) {
        got.a = await ƒ() // gets as far as this line but ƒ is never called
        semafore.signal()
    }
    semafore.wait()
    return got.a!
}

private class Got<A> {
    var a: A?
}

但是,如果我们要求在另一个上下文中执行闭包,例如通过使用自定义的全局参与者对参数进行注释,则此解决方案在macOS和iOS上都有效:

代码语言:javascript
复制
@globalActor public actor BlockingActor {
    public static let shared = BlockingActor()
}

@available(*, deprecated, message: "For use only to allow incremental migration to structured concurrency")
public func blocking<A>(_ ƒ: @BlockingActor @escaping () async -> A) -> A {
    let semaphore = DispatchSemaphore(value: 0)
    let got = Got<A>()
    Task(priority: .high) {
        got.a = await ƒ()
        semaphore.signal()
    }
    semaphore.wait()
    return got.a!
}

唉,嵌套调用仍然会导致挂起:

代码语言:javascript
复制
count += blocking {
    blocking { // moving to the same executor, so we hang as before
        1
    }
}

当然,这只是一个特殊的例子,说明了必须禁止使用任何此类API的根本原因。但是,请注意,我们已经有相当多的现有API类似地被禁止,但确实存在是有充分理由的。

一个特别相关的例子是sync方法的DispatchQueue,如果您使用它来调度任务以等待自身,它将以完全相同的方式导致挂起。尽管如此,多年来sync方法还是得到了很好的应用。

票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/272943

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档