如何确保DispatchQueue专门在主线程上执行一些代码? [英] How do I ensure my DispatchQueue executes some code on the main thread specifically?

查看:98
本文介绍了如何确保DispatchQueue专门在主线程上执行一些代码?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个管理数组的单例.可以从多个线程访问此单例,因此它具有自己的内部DispatchQueue来管理跨线程的读/写访问.为简单起见,我们将其称为串行队列.

I have a singleton that manages an array. This singleton can be accessed from multiple threads, so it has its own internal DispatchQueue to manage read/write access across threads. For simplicity we'll say it's a serial queue.

有一段时间,单例将从数组中读取并更新UI.我该如何处理?

There comes a time where the singleton will be reading from the array and updating the UI. How do I handle this?

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

Which thread my internal dispatch queue is not known, right? It's just an implementation detail I'm to not worry about? In most cases this seems fine, but in this one specific function I need to be sure it uses the main thread.

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

Is it okay to do something along the lines of:

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 { ... },并且从主线程调用了该函数,是否可以确定我的内部调度队列将在与调用它相同的(主)线程上执行该函数?还是这可能是实现细节",但也可以在后台线程上调用它?
  1. Is that okay? It seems weird/wrong to be nesting dispatch queues. Is there a better way? Maybe something like myDispatchQueue.sync(forceMainThread: true) { ... }?
  2. If I DID NOT use DispatchQueue.main.async { ... }, and I called the function from the main thread, could I be sure that my internal dispatch queue will execute it on the same (main) thread as what called it? Or is that also an "implementation detail" where it could be, but it could also be called on a background thread?

基本上,我感到困惑的是,线程似乎是您不应该为队列担心的实现细节,但是当您确实需要担心时,在偶然的机会上会发生什么呢?

Basically I'm confused that threads seem like an implementation detail you're not supposed to worry about with queues, but what happens on the odd chance when you DO need to worry?

简单的示例代码:

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]
            }
        }
    }
}

推荐答案

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

Yes, you can nest a dispatch to one queue inside a dispatch to another queue. We frequently do so.

但是要非常小心.仅使用来自同步队列的分派将异步分派包装到主队列中是不够的.您的第一个示例不是线程安全的.您正在从主线程访问的数组可能正在从同步队列中更改:

But be very careful. Just wrapping an asynchronous dispatch to the main queue with a dispatch from your synchronizing queue is insufficient. Your first example is not thread safe. That array that you are accessing from the main thread might be mutating from your synchronization queue:

这是竞赛条件,因为您可能有多个线程(您同步队列的线程和主线程)与同一集合进行交互.您应该复制它,而不是直接将objects分配到主队列中,而应直接复制objects.

This is a race condition because you potentially have multiple threads (your synchronization queue’s thread and the main thread) interacting with the same collection. Rather than having your dispatched block to the main queue just interact objects directly, you should make a copy of of it, and that’s what you reference inside the dispatch to the main queue.

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

For example, you might want to do the following:

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.

That ensures that the main queue is not interacting with any internal properties of this class, but rather just the result that was created in this closure passed to syncQueue.

请注意,所有这些都与单身无关.但是,既然您提出了这个话题,建议您不要使用单例模型数据.对于接收器,无状态控制器等而言,这是很好的选择,但通常不建议使用模型数据.

Note, all of this is unrelated to it being a singleton. But since you brought up the topic, I’d advise against singletons for model data. It’s fine for sinks, stateless controllers, and the like, but not generally advised for model data.

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

I’d definitely discourage the practice of initiating UI controls updates directly from the singleton. I’d be inclined to provide these methods completion handler closures, and let the caller take care of the resulting UI updates. Sure, if you want to dispatch the closure to the main queue (as a convenience, common in many third party API), that’s fine. But the singleton shouldn’t be reaching in and update UI controls itself.

我假设您所做的所有这些只是出于说明的目的,但我向可能不理解这些问题的未来读者增加了此警告.

I’m assuming you did all of this just for illustrative purposes, but I added this word of caution to future readers who might not appreciate these concerns.

这篇关于如何确保DispatchQueue专门在主线程上执行一些代码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆