首页 > 解决方案 > 如何确保我的 DispatchQueue 专门在主线程上执行一些代码?

问题描述

我有一个管理数组的单例。这个单例可以从多个线程访问,所以它有自己的内部DispatchQueue来管理跨线程的读/写访问。为简单起见,我们会说它是一个串行队列。

有时单例将从数组中读取并更新 UI。我该如何处理?

我的内部调度队列不知道哪个线程,对吧?这只是一个我不用担心的实现细节?在大多数情况下,这看起来不错,但在这一特定功能中,我需要确保它使用主线程。

可以按照以下方式做一些事情:

myDispatchQueue.sync { // Synchronize with internal queue to ensure no writes/reads happen at the same time
    DispatchQueue.main.async { // Ensure that it's executed on the main thread
        for item in internalArray {
            // Pretend internalArray is an array of strings
            someLabel.text = item
        }
    }
}

所以我的问题是:

  1. 可以吗?嵌套调度队列似乎很奇怪/错误。有没有更好的办法?也许像myDispatchQueue.sync(forceMainThread: true) { ... }什么?
  2. 如果我没有使用DispatchQueue.main.async { ... },并且我从主线程调用了该函数,我能否确定我的内部调度队列将在调用它的相同(主)线程上执行它?或者这也是一个“实现细节”,但它也可以在后台线程上调用?

基本上我很困惑线程似乎是一个你不应该担心队列的实现细节,但是当你确实需要担心时会发生什么?

简单示例代码:

class LabelUpdater {
    static let shared = LabelUpdater()

    var strings: [String] = []
    private let dispatchQueue: dispatchQueue

    private init {
        dispatchQueue = DispatchQueue(label: "com.sample.me.LabelUpdaterQueue")
        super.init()
    }

    func add(string: String) {
        dispatchQueue.sync {
            strings.append(string)
        }
    }

    // Assume for sake of example that `labels` is always same array length as `strings`
    func updateLabels(_ labels: [UILabel]) {
        // Execute in the queue so that no read/write can occur at the same time.
        dispatchQueue.sync {
            // How do I know this will be on the main thread? Can I ensure it?
            for (index, label) in labels.enumerated() {
                label.text = strings[index]
            }
        }
    }
}

标签: iosswiftmultithreadingcocoa-touchgrand-central-dispatch

解决方案


是的,您可以将一个调度嵌套到另一个队列的调度中。我们经常这样做。

但要非常小心。仅使用同步队列中的调度将异步调度包装到主队列是不够的。您的第一个示例不是线程安全的。您从主线程访问的那个数组可能正在从您的同步队列中发生变化:

在此处输入图像描述

这是一个竞争条件,因为您可能有多个线程(同步队列的线程和主线程)与同一个集合进行交互。与其让你的调度块objects直接交互到主队列,你应该复制它,这就是你在调度到主队列中引用的内容。

例如,您可能想要执行以下操作:

func process(completion: @escaping (String) -> Void) {
    syncQueue.sync {
        let result = ...            // note, this runs on thread associated with `syncQueue` ...

        DispatchQueue.main.async {
            completion(result)      // ... but this runs on the main thread
        }
    }
}

这确保了主队列不与此类的任何内部属性交互,而只是在result传递给syncQueue.


请注意,所有这些都与它是单例无关。但既然你提出了这个话题,我建议不要使用单例来获取模型数据。它适用于接收器、无状态控制器等,但通常不建议用于模型数据。

我绝对不鼓励直接从单例启动 UI 控件更新的做法。我倾向于提供这些方法完成处理程序闭包,并让调用者处理生成的 UI 更新。当然,如果您想将闭包分派到主队列(为方便起见,在许多第三方 API 中很常见),那很好。但是单例不应该进入并更新 UI 控件本身。

我假设您所做的所有这些只是为了说明目的,但我向可能不理解这些担忧的未来读者添加了这个警告。


推荐阅读