为什么DispatchWorkItem通知崩溃?

时间:2017-12-22 16:46:33

标签: swift queue grand-central-dispatch swift4 dispatchworkitem

我刚开始用Swift编程语言学习更多关于Grand Central Dispatch的知识。

我在线阅读了一个教程,以便更好地理解GCD,并尝试了各种使用示例......

在关于工作项的部分中,我编写了以下代码:

func useWorkItem() {
    var value = 10
    let workItem = DispatchWorkItem {
        value += 5
    }

    workItem.perform()
    let queue = DispatchQueue.global(qos: .utility)
    queue.async(execute: workItem)

    workItem.notify(queue: DispatchQueue.main) {
        print("value = ", value)
    }
}

代码基本上在两个不同的队列(主队列和全局队列)中执行workItem,当工作项在两个队列中完成运行时,我得到了结果。

上面代码的输出是:20。

当我尝试稍微操作代码并将另一个Queue添加到混合中并使用与全局队列(qos)相同的.utility运行相同的workItem时,如下所示:

 func useWorkItem() {
    var value = 10
    let workItem = DispatchWorkItem {
        value += 5
    }

    workItem.perform()
    let queue = DispatchQueue.global(qos: .utility)
    queue.async(execute: workItem)

    let que = DispatchQueue(label: "com.appcoda.delayqueue1", qos: .utility)
    que.async(execute: workItem)

    workItem.notify(queue: DispatchQueue.main) {
        print("value = ", value)
    }
}

应用程序崩溃。

但是当我更改命令的顺序,所以我将workItem.notify方法移动到方法的开头,应用程序工作并给我正确的输出,即25:

func useWorkItem() {
    var value = 10
    let workItem = DispatchWorkItem {
        value += 5
    }

    workItem.notify(queue: DispatchQueue.main) {
        print("value = ", value)
    }

    workItem.perform()
    let queue = DispatchQueue.global(qos: .utility)
    queue.async(execute: workItem)
    let que = DispatchQueue(label: "com.appcoda.delayqueue1", qos: .utility)
    que.async(execute: workItem)
}

任何人都可以帮助了解.notify()方法是如何运作的吗? 为什么命令的顺序有所不同?

提前多多感谢...

1 个答案:

答案 0 :(得分:2)

你分享的第一个例子(我收集的是直接来自教程)由于以下几个原因而写得不好:

  1. 它正在从多个线程更新变量。这是一个固有的非线程安全的过程。事实证明,由于这里不值得概述的原因,它在技术上并不是作者原始示例中的问题,但它是一个非常脆弱的设计,通过后续示例中快速显示的非线程安全行为来说明。

    如果从多个线程操作变量,应始终synchronize访问变量。您可以使用专用的串行队列,NSLock,读写器模式或其他模式。虽然我经常使用另一个GCD队列进行同步,但我认为当我们关注DispatchWorkItem在各种队列上的GCD行为时,这会让人感到困惑,因此在下面的示例中,我将使用{ {1}}同步访问权限,在我完成后尝试使用NSLocklock()之前调用value

  2. 你说第一个例子显示“20”。这只是时间上的意外。如果你把它改成说......

    unlock

    ...然后它可能会说“15”,而不是“20”,因为在let workItem = DispatchWorkItem { os_log("starting") Thread.sleep(forTimeInterval: 2) value += 5 os_log("done") } 调用全局队列之前,您会看到notify的{​​{1}}完成。现在,你永远不会在真正的应用程序中使用workItem.perform(),但我会将其用于说明时间问题。

    底线,async上的sleep发生在首次完成调度工作项时,它不会等待后续调用它。此代码需要在您的notify块与您调度到该全局队列的调用之间所谓的“竞争条件”,并且您无法确定哪个将首先运行。

  3. 就个人而言,即使抛开竞争条件和从多个线程变异某些变量的固有非线程安全行为,我建议不要多次调用相同的DispatchWorkItem,至少结合使用在该工作项目上使用notify

  4. 如果您想在完成所有操作后发出通知,则应使用DispatchWorkItem上的notify,而不是DispatchGroup上的notify

  5. 将所有内容整合在一起,你得到的结果如下:

    DispatchWorkItem