【问题标题】:How do I use DispatchGroup in background thread?如何在后台线程中使用 DispatchGroup?
【发布时间】:2018-12-29 18:05:17
【问题描述】:

我有两个功能(或任务)要一个接一个地运行,我正在使用 DispatchGroup 跟踪它们并在它们完成时通知我。现在它们正在主线程中完成,但我想在后台线程中运行这些任务。我该怎么做呢?我尝试了几种方法,但它们要么同时运行,要么在第一个完成后出现异常错误。以下代码一个接一个地执行任务,但如果我在函数内部调用 Thread.current,我可以看到它们正在主线程中运行。

@objc func doWorkFunctions(){
    taskGroup.enter()
    DispatchQueue.global(qos: .background).sync {
        self.firstFunction {
            self.taskGroup.leave()
        }
    }

    taskGroup.enter()
    DispatchQueue.global(qos: .background).sync {
        self.secondFunction {
            self.taskGroup.leave()
        }
    }

    taskGroup.notify(queue: .main) {
        print("All tasks completed")
    }
}

如果我使用以下代码,它们会同时运行,但在后台线程中。

@objc func doWorkFunctions(){
    taskGroup.enter()
    DispatchQueue.global(qos: .background).async {
        self.firstFunction {
            self.taskGroup.leave()
        }
    }

    taskGroup.enter()
    DispatchQueue.global(qos: .background).async {
        self.secondFunction {
            self.taskGroup.leave()
        }
    }

    taskGroup.notify(queue: .main) {
        print("All tasks completed")
    }
}

我一直在寻找和寻找,但我似乎无法找到我的问题的答案或关于这件事的明确性。有人可以就这里发生的事情提供一些指导。这些是有问题的功能。他们模拟一项长期任务来练习跟踪进度。

func firstFunction(completion: @escaping()->Void){
    print(Thread.current)
    if childProgressOne.isCancelled { return }
    for i in 1...5 {
        sleep(1)

        childProgressOne.completedUnitCount = Int64(i * 20)

        print("Child Progress One: \(childProgressOne.fractionCompleted)")
        print("Total Progress: \(totalProgress.fractionCompleted)")
    }
    completion()
}

func secondFunction(completion: @escaping()->Void){
    print(Thread.current)

    if childProgressTwo.isCancelled { return }
    for i in 1...5 {
        sleep(1)

        childProgressTwo.completedUnitCount = Int64(i * 20)

        print("Child Progress Two: \(childProgressTwo.fractionCompleted)")
        print("Total Progress: \(totalProgress.fractionCompleted)")
    }
    completion()
}

这也按顺序执行它们,但是在函数内部调用 Thread.current 会告诉我它们正在主线程中执行,即使它们被调用到后台线程也是如此。

 @objc func doWorkFunctions(){
    DispatchQueue.global(qos: .background).sync {
        self.taskGroup.enter()
        self.firstFunction {
            self.taskGroup.leave()
        }
        self.taskGroup.enter()
        self.secondFunction {
            self.taskGroup.leave()
        }
    }

    taskGroup.notify(queue: .main) {
        print("All tasks completed")
    }
}

【问题讨论】:

  • 当你把它们放在后台线程中时,它们会同时运行。我不完全明白这个问题。
  • @George_E 我不希望它们同时运行。我希望它们一个接一个地在后台运行。
  • 如果您将第一个代码块与sync 一起使用并将两者放在一个DispatchQueue 块中,它是否有效?
  • 一个接一个地执行它们,但由于某种原因在函数内部调用 Thread.current 表示它仍在主线程中。
  • @George_E 用建议更新了问题。

标签: swift


【解决方案1】:

鉴于您所描述的,我可能根本不会在这里使用调度组。我只是链接方法:

@objc func doWorkFunctions() {
    DispatchQueue.global(qos: .background).async {
        self.firstFunction {
            self.secondFunction {
                DispatchQueue.main.async {
                    print("All tasks completed")
                }
        }
    }
}

但是假设你有充分的理由在这里建一个组,你需要做的是使用.notify 来同步它们。 .notify 表示“当组为空时,将此块提交到此队列。”

@objc func doWorkFunctions(){
    let queue = DispatchQueue.global(qos: .background)

    taskGroup.enter()
    queue.async {
        self.firstFunction {
            self.taskGroup.leave()
        }
    }

    taskGroup.notify(queue: queue) {
        self.taskGroup.enter()

        self.secondFunction {
            self.taskGroup.leave()
        }

        self.taskGroup.notify(queue: .main) {
            print("All tasks completed")
        }
    }
}

(您可能不需要 taskGroup 在这里成为实例属性。您可以将其设为局部变量并减少所需的 self. 引用。每个块都有对组的引用,因此它将一直存在到所有的积木都完成了。)

【讨论】:

  • 感谢罗布的回复。第一个针对预期目的效果很好,但第二个以 NSException 结束。在这两种情况下,这些功能都是在后台线程中一个接一个地执行的,但是在第二种情况下,在调试器中打印出“所有任务已完成”后,我立即收到错误消息。我想我可以将第一种方法很好地用于我打算执行的代码,这基本上是单独和整体跟踪两个函数的进度。我仍在努力了解 DispatchGroups 以及它们的使用方式和用途。
  • 什么异常? (您还记得第 4 行的第一个 .enter() 电话吗?这是最有可能的错误。)
  • 我意识到抛出异常是因为我正在上课,并且用于触发该方法的按钮的插座存在问题。在一个简单的游乐场中,您的解决方案可以完美地使用组。非常感谢。
【解决方案2】:

如果您只想在后台串行运行两个函数,那么您只需在同一队列中的同一任务中按顺序执行它们即可。根本不需要花哨。

您可以将其插入游乐场并随意使用它:

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

func firstFunction(completion: @escaping() -> Void) {
    for i in 1...5 {
        sleep(1)
        print(i, Thread.current)
    }
    completion()
}

func secondFunction(completion: @escaping() -> Void) {
    for i in 1...5 {
        sleep(1)
        print(i + 100, Thread.current)
    }
    completion()
}

func doWorkFunctions() {

    let serialQueue = DispatchQueue(label: "serial")
    //let concurrentQueue = DispatchQueue.global(qos: .default) <-- this will produce the same result

    serialQueue.async {
        firstFunction(completion: {
            print("first function done")
        })
        secondFunction(completion: {
            print("second function done")
        })
    }

}

doWorkFunctions()

无论您是在串行队列还是并发队列中执行这两个函数,以及您是同步还是异步调度它们,如果您将它们放在同一个队列中的同一个任务中,就您的问题而言,这是微不足道的。但是,如果您将这两个函数分成两个单独的任务(或队列,就此而言),那么序列化和并发性就会成为一个因素。但是,需要注意的是,“并发”一词是相对的。通过调度队列(串行或并发)执行的所有任务都与主线程并发。但是当我们在调度队列的上下文中谈论它们时,我们几乎总是指与其他任务并发。

阅读本文以更好地了解排队的确切含义:https://stackoverflow.com/a/53582047/9086770

【讨论】:

  • 非常感谢!这些信息真的很有帮助。苹果的文档在这方面非常有限。您的解决方案也非常适合预期目的。感谢您的帮助。
  • 从链接中很好地阅读。你总结得很完美,而且更清楚了。再次感谢。
  • 令人惊讶的是,这可以在实时代码中工作。这个例子在返回之前同步运行completion,这意味着它根本不是一个完成处理程序(完成处理程序在异步操作完成后运行,返回后)。更典型的例子是将firstFunctionsecondFunction 的整个主体包含在您无法控制的队列上的DispatchQueue.async 调用中,在这种情况下secondFunction 将在firstFunction 完成之前开始。
  • 这并不奇怪——两个函数被放入同一个队列中的单个任务中,并以串行方式执行。没有任何东西需要在异步操作(函数返回之前或之后)调用完成块才能被视为完成块(所有语言 AFAIK 中的所有回调都是如此)。完成块只不过是在操作的主要任务完成后运行的块。它们是否通常用于异步是另一回事,但它们的唯一目的是通知操作结束——在主线程上、在后台、同步、异步。
  • 这里的常见误解似乎是,函数firstFunction 和函数secondFunction 应该是异步,完成处理程序的@escaping 修饰符表明了这一点。然而,模拟的函数确实是同步的。 @nard 的方法仅适用于同步函数。现在,问题是,actual 函数是异步的还是同步的?如果实际功能是异步,@RobNapier's 是最简单的解决方案。
【解决方案3】:

此解决方案是前两个答案的组合。我使用了@nard 在第二个答案中发布的内容,但也使用了 DispatchGroup。我意识到在这种情况下 DispatchGroup 并不是真正需要的,但如果是的话,这将是一种方法。感谢@Rob Napier 和@nard 的指导。

import UIKit
func workOne(completion: @escaping ()->Void){
    print(Thread.current)
    for i in 1...4{
        sleep(1)
        print(i)
    }
    completion()
}
func workTwo(completion: @escaping ()->Void){
    print(Thread.current)
    for i in 5...8{
        sleep(1)
        print(i)
    }
    completion()
}
func doWork(){
    let dispatchGroup = DispatchGroup()

    dispatchGroup.enter()
    workOne {
        dispatchGroup.leave()
    }
    dispatchGroup.enter()
    workTwo {
        dispatchGroup.leave()
    }
    dispatchGroup.notify(queue: .main) {
        print(Thread.current)
        print("completed!")
    }
}
DispatchQueue.global(qos: .default).async {
    doWork()
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-26
    • 2014-07-26
    • 1970-01-01
    • 1970-01-01
    • 2018-08-29
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多