【问题标题】:DispatchGroup in For LoopFor 循环中的 DispatchGroup
【发布时间】:2017-12-10 13:46:15
【问题描述】:

所以,我花了一些时间尝试让 DispatchGroup 在长时间的异步操作完成之前阻止 for 循环进行迭代。我发现的大多数示例都相当简单明了,但我似乎无法让我的简单测试用例按预期工作。

let group = DispatchGroup()

    for i in 1...3 {
        group.enter()
        print("INDEX \(i)")
        asynchronousOperation(index: i, completion: {
            print("HELLO \(i)")
            self.group.leave()

        })
        print("OUTSIDE \(i)")
    }


func asynchronousOperation(index: Int, completion: @escaping () -> ()) {
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+5) {
        print("DONE \(index)")
        completion()

    }
}

这最终会打印出来

START
INDEX 1
OUTSIDE 1
INDEX 2
OUTSIDE 2
INDEX 3
OUTSIDE 3
DONE 1
HELLO 1
DONE 2
HELLO 2
DONE 3
HELLO 3

我希望它能打印出更像

的东西
START
INDEX 1
OUTSIDE 1
HELLO 1
INDEX 2
OUTSIDE 2
HELLO 2
INDEX 3
OUTSIDE 3
HELLO 3

只要在 asynchronousOperation() 内部调用 group.leave() 之前,不会打印 OUTSIDE 之后的下一个“INDEX”

我可能误解了一些简单的事情——有什么想法吗?

【问题讨论】:

  • 您没有使用调度组。使用group.waitgroup.notify,该组无用。无论如何,组并不是你真正需要的。
  • 为了未来的读者,如果不承认这是一种反模式,就不能完整地讨论这个主题,除非非常特殊的情况,否则应该避免,因为如果使用不正确(而且几乎总是是),它可能会导致各种问题,无论是微妙的还是严重的。这是一种令人陶醉的模式,因为它似乎简化了一个人的网络代码,用诱人的简单和同步模式代替了复杂的异步模式。但这几乎总是错误的解决方案。
  • @Rob 你能描述一下在这种情况下首选的模式是什么吗?
  • Kumar 和 Andrea 向您展示了如何更改代码以实现“预期”输出,但它们涉及阻塞当前线程,这通常是一个坏主意(如果当前线程是主线程)。优选的模式包括(a)让它异步运行(就像你的第一个输出一样)并根据需要组织结果;或者 (b) 如果您绝对必须让一个异步任务在前一个任务完成之前不启动,请创建自定义异步 Operation 子类,并在它们之间建立依赖关系。
  • 简而言之,这个问题有点抽象,生成的答案达到了你的预期输出,但在实践中通常是错误的。如果问题是“为什么我得到了我所做的输出”(如果你仍然不清楚这个问题),我很乐意发布一个答案。如果您的问题是“我知道为什么我得到了我所做的输出,但想知道我需要做什么才能实现所需的输出”,那么我会反击并询问asynchronousOperation 正在做什么的真实示例,并且为什么要让当前线程等待它。正确的解决方案将取决于更广泛的问题是什么。

标签: swift asynchronous grand-central-dispatch


【解决方案1】:

执行以下代码以获得正确的输出:

for i in 1...3 {
    let semaphore = DispatchSemaphore(value: 0) // create a semaphore with a value 0. signal() will make the value 1.
    print("INDEX \(i)")
    asynchronousOperation(index: i, completion: {
        print("HELLO \(i)")
        semaphore.signal() // once you make the signal(), then only next loop will get executed.
    })
    print("OUTSIDE \(i)")
    semaphore.wait() // asking the semaphore to wait, till it gets the signal.
}

   func asynchronousOperation(index: Int, completion: @escaping () -> ()) {
    DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+5) {
        print("DONE \(index)")
        completion()
    }
}

输出:

索引 1 外 1 完成 1 你好 1

索引 2 外面 2 完成 2 你好 2

索引 3 外 3 完成 3 你好 3

【讨论】:

  • 谢谢。这解决了这个问题。我需要更多地研究信号量在 GDC 中是如何工作的。谢谢。
  • 微小的变化,但你不需要在循环的每次迭代中创建一个新的DispatchSemaphore。我将let semaphore = ... 拉到循环之外并在for 之前声明它。
【解决方案2】:

为了实现这一点,如果我理解正确,您可能需要使用 DispatchSemaphore。这样做:

let semaphore = DispatchSemaphore(value: 1)

for i in 1...3 {
   self.semaphore.wait()
   print("INDEX \(i)")
   asynchronousOperation(index: i, completion: {
     print("HELLO \(i)")
     self.semaphore.signal()
   })
   print("OUTSIDE \(i)")
}


func asynchronousOperation(index: Int, completion: @escaping () -> ()) {
  DispatchQueue.global().asyncAfter(deadline: DispatchTime.now()+5) {
    print("DONE \(index)")
    completion()
  }
}

【讨论】:

  • 您希望对wait 的调用在对asynchronousOperation 的调用之后(就在print("OUTSIDE") 行之前或之后。
  • @AndreaMugnaini 这很接近,但在使用DispatchSemaphore(value: 1) 初始化信号量时不太适用。 @KumarReddy 对DispatchSemaphore(value: 0) 的回答产生了预期的流量。现在我知道我需要在哪里学习以了解为什么会有所作为。谢谢。
  • @rmaddy 我尝试将此代码用于我的循环,但它给出了XPS connection error。而且我还尝试使用 DispatchGroup 并在仅使用 Apple id paste.ubuntu.com/p/cs2XYj6gdD 进行身份验证时得到这样的日志
  • @rmaddy - 不,我是针对你的(我很抱歉重新打开一个非常古老的线程;哈哈)。您的评论说要移动wait 电话,但安德里亚将它放在正确的位置。如果调度组是用0 而不是1 初始化的(即传统的“让我们一次做一个”模式),您关于移动它的评论将是有意义的。但是,如果您对 DispatchGroup 使用正值(不太常见但仍然有效,“一次运行 x”模式),那么 Andrea 的代码是正确的,将 wait 调用移动为你建议。
  • FWIW,dispatch semaphore使用正值,这里展示,一般用于“一次运行x个任务”(用来控制并发)。例如,如果您有 100 个任务要运行,但您不想在任何给定时间同时运行超过 4 个,您可以使用 DispatchSemaphore(value: 4)shown here。虽然 OPs 问题可以被认为是这种模式的一个特例,其中 x1accepted answer 是规范解决方案。
猜你喜欢
  • 1970-01-01
  • 2011-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-01
  • 2020-10-18
  • 2021-07-30
  • 1970-01-01
相关资源
最近更新 更多