由于对调度组的不平衡调用导致不明显的崩溃

Posted

技术标签:

【中文标题】由于对调度组的不平衡调用导致不明显的崩溃【英文标题】:Unobvious crash due to unbalanced call to dispatch group 【发布时间】:2020-01-16 12:39:56 【问题描述】:

我收到了一份崩溃报告BUG IN CLIENT OF LIBDISPATCH: Unbalanced call to dispatch_group_leave()

stacktrace 指向的匿名代码如下所示:

 func updateHealthKitData(from startDate: Date, to endDate: Date, completion: @escaping () -> ()) 
     let group = DispatchGroup()
     group.enter()
     self.hkDataStore.getData1(startDate: startDate, endDate: endDate, completion: (data1) in
         if let samplesToProcess = data1 
             self.processData1(data: samplesToProcess, completion: () in
                 group.leave()
             )
          else 
             group.leave()
         
     )
     group.enter()
     self.hkDataStore.getData2(startDate: startDate, completion: (data2) in
         if let samplesToProcess = data2 
             self.processData2(data: samplesToProcess, completion: () in
                 group.leave()
             )
          else 
             group.leave()
         
     )

     group.notify(queue: .main) 
         completion()
     
 

异常来自队列com.apple.HealthKit.HKHealthStore.client。 任务getData2getData1 查询后台队列中的health-kit。

我读到调度组是线程安全的,即它可以在没有synchronize 的情况下从其他队列中使用。 Group dispatch 在执行任务之前进入两次,然后才等待 balance 通知任务何时完成。 我仔细检查了任何完成(和嵌套的)没有被调用两次,这可能会导致对调度组的额外调用。

这里有什么问题?提前感谢您的任何帮助和想法!

【问题讨论】:

当我将调度组与 healthkit 一起使用时,我也遇到了同样的问题。我尝试在闭包内使用 defer 以避免两次 leave() 调用,但应用程序仍然因相同的错误而崩溃。 【参考方案1】:

首先,你是对的,DispatchGroup 确实是线程安全的。这不是问题。

如果您leave 的次数多于您enter 的次数,则会收到此“不平衡呼叫”错误。

因此,getData1processData1getData2processData2 这四个例程中的一个或多个必须多次调用其各自的完成处理程序。在这种情况下,对于一个 enter 呼叫,您有多个 leave 呼叫。

我们需要查看这些 getDataprocessData 例程来进一步诊断它,但我怀疑你能找到它。


你说:

我仔细检查了任何完成(和嵌套的)都没有被调用两次,这可能会导致对调度组的额外调用。

不过,这一定是问题所在。我建议通过经验来验证这一点,或许可以暂时添加日志语句,或许还可以添加一个布尔测试,例如:

func updateHealthKitData(from startDate: Date, to endDate: Date, completion: @escaping () -> ()) 
    let group = DispatchGroup()
    group.enter()
    var hasRunAlready1 = false
    self.hkDataStore.getData1(startDate: startDate, endDate: endDate, completion: (data1) in
        if let samplesToProcess = data1 
            self.processData1(data: samplesToProcess, completion: () in
                if hasRunAlready1  fatalError("Has run 1 already - a") 
                hasRunAlready1 = true
                print("1a")
                group.leave()
            )
         else 
            if hasRunAlready1  fatalError("Has run 1 already - b") 
            hasRunAlready1 = true
            print("1b")
            group.leave()
        
    )

    group.enter()
    var hasRunAlready2 = false
    self.hkDataStore.getData2(startDate: startDate, completion: (data2) in
        if let samplesToProcess = data2 
            self.processData2(data: samplesToProcess, completion: () in
                if hasRunAlready2  fatalError("Has run 2 already - a") 
                hasRunAlready2 = true
                print("2a")
                group.leave()
            )
         else 
            if hasRunAlready2  fatalError("Has run 2 already - b") 
            hasRunAlready2 = true
            print("2b")
            group.leave()
        
    )

    group.notify(queue: .main) 
        completion()
    

【讨论】:

以上是关于由于对调度组的不平衡调用导致不明显的崩溃的主要内容,如果未能解决你的问题,请参考以下文章

Swift 伪原子并发同步代码引起 DispatchGroup.leave() 方法不平衡调用导致 App 崩溃的解决

对 ParentViewController 的开始/结束外观转换的不平衡调用

CKPresentationControllerRootViewController 开始/结束外观转换的不平衡调用

警告:对 QLRemotePreviewContentController 的开始/结束外观转换的不平衡调用

UISplitViewController 中对开始/结束外观转换的不平衡调用

iOS 7 UINavigationController 对开始/结束外观转换的不平衡调用