【问题标题】:How do I ensure my DispatchQueue executes some code on the main thread specifically?如何确保我的 DispatchQueue 专门在主线程上执行一些代码?
【发布时间】:2020-04-24 05:14:54
【问题描述】:

我有一个管理数组的单例。这个单例可以从多个线程访问,因此它有自己的内部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]
            }
        }
    }
}

【问题讨论】:

  • 发布您的线程安全单例,以便我们查看。
  • 要回答您的问题,当您需要在主线程上执行某个后台任务的某些部分时,嵌套调度队列是强制性的。您的示例代码的概念是标准做法。我将如何实际实现它是另一回事。
  • 别再担心线程了,想想队列吧?
  • 我什至不知道“是否保证在主线程上也调用函数 B 的调度队列”是什么意思。如果您在某个队列上调用一个方法,那么显然会在该队列上调用它。
  • “保证从主线程执行“Hello world”的打印“什么??保证从调度队列 test.test.test2 中执行。

标签: ios swift multithreading cocoa-touch grand-central-dispatch


【解决方案1】:

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

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

这是一个race condition,因为您可能有多个线程(同步队列的线程和主线程)与同一个集合进行交互。与其让你的调度块直接与objects 交互,不如直接与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 控件。

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

【讨论】:

  • 如果没有真正通过的结果怎么办?为了详细说明我的具体用例(正如您猜想的那样,我主要是编造示例),这与应用程序的主题有关,其中基本上是一堆 listener 对象(采用协议的 UIViews、UIViewControllers 等)被告知主题更改,然后调用他们的协议让他们更新。这没有返回值,它是一劳永逸的。但是由于 UIKit,我需要在添加(写入)的新侦听器和通知所有(读取)它们(必须在主线程上发生)之间进行同步。
  • 我不明白这个问题:如果没有要传递的值,那么只需定义你的闭包不带任何参数,例如() -> Void 并将其分派到主队列。或者,与其构建自己的观察系统,不如使用NotificationCenteradd observers,指定.main 操作队列。
  • 有一些值要传递,listeners 数组(我的意思是我没有从闭包中收到值)。这个数组本质上是循环的,每个监听器都被通知并被告知更新它的主题。这必须在主线程上完成,但我还必须确保在发生这种情况时不会写入 listeners。我幼稚的解决方案类似于myDispatchQueue.sync { DispatchQueue.main.async { self.listeners.forEach { $0.theme() } } },我认为它不起作用?
  • 这个想法很好,但它不是线程安全的。创建该listeners 数组的副本并在...main.async 闭包中使用该副本。
  • 是的,应该没问题。您需要数组的副本,而不是视图控制器的副本。请注意,您可能需要确保您的 listeners 不会阻止您的视图控制器被释放(如果您还没有)。
【解决方案2】:

尝试使用 OperationQueues(Operations),因为它们确实有状态:

  • isReady:准备开始
  • isExecuting:任务当前正在运行
  • isFinished:流程完成后
  • isCancelled:任务取消

操作队列的好处:

  • 确定执行顺序
  • 观察它们的状态
  • 取消操作

可以暂停、恢复和取消操作。一旦你派出 使用 Grand Central Dispatch 的任务,您不再拥有控制权或 洞察该任务的执行情况。 NSOperation API 更多 在这方面灵活,让开发人员可以控制 操作的生命周期

https://developer.apple.com/documentation/foundation/operationqueue

https://medium.com/@aliakhtar_16369/concurrency-in-swift-operations-and-operation-queue-part-3-a108fbe27d61

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-15
    • 1970-01-01
    • 2017-11-21
    • 2012-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多