首页 > 解决方案 > 为什么 WWDC 演讲建议在主线程上运行非 UIKit 代码来修复数据竞争?

问题描述

我正在观看这个名为Thread Sanitizer and Static Analysis的WWDC 演讲,演讲者向我们展示了如果两个不同的线程调用存在数据竞争notifyStartNetworkActivity

var activityCount: Int = 0

public class ActivityCounter : NSObject {
    public func notifyStartNetworkActivity() {
        activityCount = activityCount + 1
        self.updateNetworkActivityUI()
    }
    
    func updateNetworkActivityUl() {
        WWDCJSONOperation.prepareNetworkActivity()
        if activityCount > 0 {
            WWDCJSONOperation.visibilityTimer?.invalidate()
            WWDCJSONOperation.visibilityTimer = nil
            UIApplication.shared().isNetworkActivityIndicatorVisible = true
        } else {
            /* To prevent the indicator from flickering on and off, we delay the hiding of the indicator by one second. This provides the chance to come in and invalidate the timer before it fires. */
            WWDCJSONOperation.visibilityTimer = Timer.scheduledTimer(timelnterval: 1.0, target: self, selector: #selector(ActivityCounter.fire(timer:)))
...

6 点 45 分,演讲者接着说:

现在,我可以通过添加锁来修复这场比赛。但请注意,这只是一个症状。此处的下一行更新 UI。我们知道 UI 更新应该发生在主线程上。因此,这里的正确解决方法是使用 Grand Central Dispatch 将计数器增量和 UI 更新分派到主队列中。这既可以解决我们应用程序中的逻辑问题,也可以解决竞争问题,因为所有线程都将从同一个线程访问该计数变量。

public func notifyStartNetworkActivity() {
    DispatchQueue.main.async {
        activityCount = activityCount + 1
        self.updateNetworkActivityUI()
    }
}

我对这个修复的问题是我们在主线程中添加了不必要的工作。显然 UIKit 调用UIApplication.shared().isNetworkActivityIndicatorVisible = true需要在主线程上完成,因为 UIKit 不是线程安全的。但是在主线程上做了一些不必要的工作,比如更新activityCount。正如我看过的其他 WWDC 演讲中所解释的那样,不必要的工作很糟糕,包括在 iOS 9 中为 iPad 上的多任务优化应用程序

因此,保持应用程序响应的最重要的事情是在主线程上做尽可能少的工作。主线程的首要任务是响应用户事件,而在你的主线程上做不必要的工作意味着主线程响应用户事件的时间更少。

因此,在这种情况下,我会使用锁或 GCD 队列来控制访问。虽然这些增加了开销,但这种开销被添加到执行网络操作的后台线程中,因此我们可以使 UI 尽可能地响应。然而,演讲者显然比我更了解多线程,所以我很好奇为什么在这种情况下演讲者说正确的修复涉及将非 UIKit 工作添加到主线程。

标签: iosmultithreadinguikitthread-safetygrand-central-dispatch

解决方案


增加和更新这个模型属性的工作量是无关紧要的,并且考虑到说话者无论如何都需要将 UI 更新分派到主线程,在那里增加计数器确实是最好的解决方案。

这是一个非常常见的场景,我们将模型和 UI 更新分派到主队列。只要您明智地限制您向主线程发送的内容(以及频率),就应该没问题。但由于我们必须在主线程上执行 UI 更新,无论如何,包括在那里进行微不足道的模型更新以消除任何数据竞争,也是谨慎的做法。

因此,执行计数器对象的简单增量绝对适合在主队列上执行,尤其是因为说话者必须将 UI 更新分派到主线程,无论如何。在这样一个简单的场景中引入另一种同步机制将是不必要的复杂化。


推荐阅读