【问题标题】:Occasional crash in Swift async/await concurrency code - only in release buildsSwift 异步/等待并发代码中的偶尔崩溃 - 仅在发布版本中
【发布时间】:2022-02-10 21:26:38
【问题描述】:

我在一些使用 Swift 新并发特性的代码中偶尔会遇到崩溃。这种崩溃似乎从未发生在开发版本上,无论是在模拟器中还是当我直接从 Xcode 在设备上安装代码时。然而,当人们从 TestFlight 安装代码时,这种情况经常发生。

实际的崩溃是这样的:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000040
Exception Codes: 0x0000000000000001, 0x0000000000000040

Thread 6 name:
Thread 6 Crashed:
0   libswift_Concurrency.dylib          0x00000001e6df7440 swift::TaskGroup::offer(swift::AsyncTask*, swift::AsyncContext*) + 504 (TaskGroup.cpp:603)
1   libswift_Concurrency.dylib          0x00000001e6df3c5c swift::AsyncTask::completeFuture(swift::AsyncContext*) + 132 (Task.cpp:180)
2   libswift_Concurrency.dylib          0x00000001e6df54d0 completeTaskAndRelease(swift::AsyncContext*, swift::SwiftError*) + 128 (Task.cpp:323)
3   libswift_Concurrency.dylib          0x00000001e6df5425 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 (Task.cpp:365)

我不确定这与哪一行代码相对应,但很可能在这里的某个地方:

class MyClass {

    var results: [String: Set<String>] = [:]

    func getSingleResult(index: Int) async -> (String, Set<String>) {
        // ....
    }

    func getAllResults(range: ClosedRange<Int>) async -> [String: Set<String>] {
        await withTaskGroup(
                of: (String, Set<String>).self,
                returning: [String: Set<String>].self
            ) { [self] group in
                for i in range {
                    group.addTask { await getSingleResult(index: i) }
                }

                var results: [String: Set<String>] = [:]

                for await result in group {
                    results[result.0] = result.1
                }

                return results
            }
    }
    
    func blockingDoWork(range: ClosedRange<Int>) {
        results = [:]

        let dispatchGroup = DispatchGroup()
        dispatchGroup.enter()

        Task.init {
            results = await getAllResults(range: range)
            dispatchGroup.leave()
        }
        
        dispatchGroup.wait()

        // Now do something with `results`
    }

我试图弥合同步代码和异步代码之间的鸿沟(可能是错误的方式)。基本上我有一个单线程/同步代码,然后创建可变数量的异步调用并尝试阻塞,直到所有这些调用完成。它将这些调用的结果汇总到 results 类成员中,这可能不是最好的方法,但却是我可以让异步代码与同步代码进行通信的唯一方法。

此代码在开发版本中似乎运行良好,在发布版本中运行了 1000 次,但随后崩溃。

我似乎无法打开线程清理器或地址清理器,因为我的 Xcode 项目使用 Swift 包管理器,并且在使用这两者时存在导致构建失败的错误。

有什么想法可能会出错吗?我假设我对开发构建很幸运,并且我在这段代码中遇到了一些基本问题,但我对 Swift 新并发功能的微妙之处了解不足,无法识别它。

【问题讨论】:

    标签: swift async-await


    【解决方案1】:

    您不能将信号量与异步等待结合使用。见Swift concurrency: Behind the scenes

    [Primitives] 像信号量 ... 与 Swift 并发一起使用是不安全的。这是因为它们对 Swift 运行时隐藏了依赖信息,但在代码的执行中引入了依赖。由于运行时不知道这种依赖关系,它无法做出正确的调度决策并解决它们。特别是,不要使用创建非结构化任务的原语,然后通过使用信号量或不安全原语追溯引入跨任务边界的依赖关系。这样的代码模式意味着一个线程可以无限期地阻塞信号量,直到另一个线程能够解除阻塞。这违反了线程前向进度的运行时契约。

    您可以考虑在同一视频中使用 LIBDISPATCH_COOPERATIVE_POOL_STRICT 环境变量作为 discussed here 进行测试。

    你问:

    我正在尝试弥合同步代码和异步代码之间的鸿沟(可能是错误的方式)。

    您应该将调用此同步方法的代码重构为采用异步模式,然后删除所有阻塞 API(例如,信号量wait、调度组wait 等)。这些是 GCD 世界中的反模式,在 Swift 并发中应该避免。我理解为什么不熟悉异步编程的开发人员会如此热衷于那些同步反模式,但这一直是一个错误,应该从代码中删除。

    底线是,在 Swift 并发中,必须“维护一个运行时契约,线程总是能够向前推进。”只需采用异步模式(即,在没有任何老式线程阻塞技术的情况下保持在 async-await 范围内),你就可以了。

    FWIW,Swift concurrency: Update a sample app 展示了用于增量更新旧应用程序的有趣技术。例如,将此阻塞方法标记为已弃用,然后编译器会警告您调用它的位置,您可以将重构工作定向到那些有问题的例程。

    【讨论】:

    • 感谢您快速详细的回答。我认为这可能是使用新语言功能的好机会,但显然它不适合这种情况。
    • 如果你不介意我问 - 在 Swift 中实现这一点的正确模式是什么?我有一个线程需要调用具有各种不同参数的方法,这项工作可以并行完成。我想基本上在 1 到 n 个线程/任务之间分叉,然后阻塞直到它们全部完成,将它们的结果收集到字典中。
    • 很公平。我建议摆脱这种同步模式,因为(a)这是一个糟糕的用户体验,用户界面会冻结;并且 (b) 如果您在错误的时间执行此操作,watchdog process 可能会毫不客气地杀死您的应用程序(当用户不幸蜂窝连接不佳且网络请求很慢时尤其容易受到影响)。你永远不应该阻塞主线程。所以,即使你决定推迟采用 Swift 并发,你真的应该摆脱这种同步模式。
    • 如果您正在执行一系列任务(本质上是异步的)并且您希望在它们全部完成后触发一些其他操作,您可以使用 GCD 中的调度组,或者使用 GCD 中的任务组快速并发。例如,stackoverflow.com/a/70988290/1271826 说明了这两种模式。
    • DispatchGroup 可以与 Task 一起使用(不使用 async/await)吗?例如,这是否安全(抛开线程阻塞的问题):
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 2017-01-08
    • 2017-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-23
    相关资源
    最近更新 更多