英文:
How do I ensure my DispatchQueue executes some code on the main thread specifically?
问题
I have translated the code portion as requested:
我有一个管理数组的单例。 这个单例可以从多个线程访问,因此它有自己的内部 `DispatchQueue` 来管理跨线程的读/写访问。 为了简单起见,我们将其称为串行队列。
有一个时刻,单例将从数组中读取并更新 UI。 我该如何处理?
我不知道我的内部调度队列是哪个线程,对吗? 这只是一个我不需要担心的实现细节吗? 在大多数情况下,这似乎都没问题,但在这一个特定的函数中,我需要确保它使用主线程。
这样做可以吗?:
```swift
myDispatchQueue.sync { // 与内部队列同步以确保不会同时发生写入/读取
DispatchQueue.main.async { // 确保它在主线程上执行
for item in internalArray {
// 假装 internalArray 是一个字符串数组
someLabel.text = item
}
}
}
所以我的问题是:
- 这样做可以吗? 嵌套调度队列似乎有点奇怪/不对劲。 有没有更好的方法? 也许像
myDispatchQueue.sync(forceMainThread: true) { ... }
这样的东西? - 如果我没有使用
DispatchQueue.main.async { ... }
,而是在主线程中调用函数,我可以确保我的内部调度队列会在与调用它的主线程上执行吗? 还是那也是一个“实现细节”,可以在后台线程上调用它?
基本上,我感到困惑的是线程似乎是一个你不应该担心的实现细节,但在你确实需要担心的时候会发生什么?
Please note that the code contains some HTML escape codes (e.g., `"`) which you might need to replace with their respective characters in Swift code.
<details>
<summary>英文:</summary>
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.
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:
```swift
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
}
}
}
So my questions are:
- Is that okay? It seems weird/wrong to be nesting dispatch queues. Is there a better way? Maybe something like
myDispatchQueue.sync(forceMainThread: true) { ... }
? - 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?
Simple example code:
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]
}
}
}
}
答案1
得分: 3
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" 交互,您应该复制它,并在分派到主队列中引用它。
例如,您可以执行以下操作:
func process(completion: @escaping (String) -> Void) {
syncQueue.sync {
let result = ... // 注意,这在与 syncQueue
关联的线程上运行...
DispatchQueue.main.async {
completion(result) // ...但这在主线程上运行
}
}
}
这确保主队列不与此类的任何内部属性进行交互,而只是在传递给 syncQueue
的闭包中创建的 result
。
注意,所有这些与它是单例无关。但既然您提到了这个话题,我建议不要将单例用于模型数据。这对于接收器、无状态控制器等是可以的,但通常不建议用于模型数据。
我绝对不鼓励直接从单例开始更新 UI 控件的做法。我倾向于为这些方法提供完成处理程序闭包,并让调用者负责结果的 UI 更新。当然,如果您想将闭包分派到主队列(作为方便,在许多第三方 API 中很常见),那是可以的。但是单例不应该直接进行 UI 控件的更新。
我假设您只是出于说明目的做了所有这些事情,但我添加了这个警告词,以便未来的读者可能不会重视这些问题。
英文:
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:
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
}
}
}
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.
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.
答案2
得分: 0
尝试使用OperationQueues(Operations),因为它们具有状态:
- isReady:准备好开始
- isExecuting:任务当前正在运行
- isFinished:一旦进程完成
- isCancelled:任务被取消
Operation Queues的好处:
- 确定执行顺序
- 观察它们的状态
- 取消操作
> 操作可以暂停、恢复和取消。一旦使用Grand Central Dispatch分派任务,您将不再对该任务的执行具有控制或洞察力。在这方面,NSOperation API更加灵活,为开发人员提供了对操作生命周期的控制。
https://developer.apple.com/documentation/foundation/operationqueue
英文:
Try using OperationQueues(Operations) as they do have states:
- isReady: It’s prepared to start
- isExecuting: The task is currently running
- isFinished: Once the process is completed
- isCancelled: The task canceled
Operation Queues benefits:
- Determining Execution Order
- observe their states
- Canceling Operations
> Operations can be paused, resumed, and cancelled. Once you dispatch a
> task using Grand Central Dispatch, you no longer have control or
> insight into the execution of that task. The NSOperation API is more
> flexible in that respect, giving the developer control over the
> operation’s life cycle
https://developer.apple.com/documentation/foundation/operationqueue
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论