队列何时会认为任务已完成?

When would a queue consider a task is completed?

在下面的代码中,queueT(串行队列)什么时候认为“任务 A”已完成?
aNetworkRequest 切换到另一个线程的那一刻?
或者在 doneInAnotherQueue 块中? (评论// 1)

换句话说,“任务B”什么时候执行?

let queueT = DispatchQueue(label: "com.test.a")
queueT.async { // task A
    aNetworkRequest.doneInAnotherQueue() { // completed in another thread possibly
        // 1
    }
}

queueT.async { // task B
    print("It's my turn") 
} 

如果你能解释一下队列如何认为任务已完成的机制,那就更好了。
提前致谢。

你可以做一个简单的检查

    let queueT = DispatchQueue(label: "com.test.a")
    queueT.async { // task A
        
        DispatchQueue(label: "com.test2.a").async { // create another queue inside 
            for i in 0..<6 {
                print(i)
            }
        }
      
    }

    queueT.async { // task B
        for i in 10..<20 {
            print(i)
        }
    }

  
}

你每次都会得到不同的输出运行这意味着当你切换线程时任务被认为完成

你的任务 A return马上就来。将工作分派到另一个队列是同步的。将 'doneInAnotherQueue' 之后的块(尾随闭包)视为 doneInAnotherQueue 函数的参数,与传递 Int 或 String 没有区别。你传递那个块,然后你 return 立即使用来自任务 A 的右括号

当闭包通过 returns 时,GCD 工作项完成。因此,对于您的示例,我将重写它以使函数调用和参数更加明确(而不是使用尾随闭包语法)。

queueT.async(execute: {
    // This is a function call that takes a closure parameter. Whether this
    // function returns, then this closure will continue. Whether that is before or
    // after running completionHandler is an internal detail of doneInAnotherQueue.
    aNetworkRequest.doneInAnotherQueue(closureParameter: { ... })

    // At this point, the closure is complete. What doneInAnotherQueue() does with
    // its closure is its business.
})

假设 doneInAnotherQueue() 在“将来的某个时候”执行它的闭包参数,那么你的任务 B 可能会 运行 在闭包 运行 之前(它可能不会;它真的那时的一场比赛,但可能)。如果 doneInAnotherQueue() 在 returning 之前阻塞关闭,那么 closureParameter 肯定会在任务 B.运行 之前

这里绝对没有魔法。系统不知道 doneInAnotherQueue 对其参数做了什么。它可能 永远不会 运行 它。它可能 运行 它立即。它可能 运行 将来某个时候。系统只是调用 doneInAnotherQueue() 并传递给它一个闭包。

我用正常的“带参数的函数”语法重写了 async 以更清楚地表明 async() 只是一个函数,它需要一个闭包参数。 不是魔术。它不是语言的一部分。它只是 Dispatch 框架中的一个普通函数。它所做的一切都是获取它的参数,将其放入调度队列,然后 return。它不执行任何操作。只有闭包被放入队列、计划和执行。

Swift 正在添加 structured concurrency,这将添加更多语言级并发功能,使您能够表达比 GCD 提供的简单原语更高级的东西。

简而言之,第一个示例启动了一个异步网络请求,因此 async 调用会在该网络请求提交后立即“完成”(但不会等待该网络请求完成)。

我假设真正的问题是您想知道网络请求何时完成。总之,GCD 不太适合管理任务之间的依赖关系,这些任务本身就是异步请求。将网络请求的发起调度到串行队列无疑不会实现您想要的。 (在有人建议使用信号量或调度组 wait 来完成异步请求之前,请注意,这可以解决战术问题,但这是一种要避免的模式,因为它对资源的使用效率低下,并且在边缘情况下,可能会引入死锁。)

一种模式是使用完成处理程序:

func performRequestA(completion: @escaping () -> Void) { // task A
    aNetworkRequest.doneInAnotherQueue() { object in
        ...
        completion()
    }
}

现在,在实践中,我们通常会使用带有参数的完成处理程序,甚至可能是 Result 类型:

func performRequestA(completion: @escaping (Result<Foo, Error>) -> Void) { // task A
    aNetworkRequest.doneInAnotherQueue() { result in
        guard ... else {
            completion(.failure(error))
            return
        }
        let foo = ...
        completion(.success(foo))
    }
}

然后您可以使用完成处理程序模式来处理结果、更新模型,并可能启动依赖于此请求结果的后续请求。例如:

performRequestA { result in
    switch result {
    case .failure(let error):
        print(error)

    case .success(let foo): 
        // update models or initiate next step in the process here
    }
}

如果您真的想问如何管理异步任务之间的依赖关系,还有许多其他优雅的模式(例如,Combine,自定义异步 Operation 子类,即将推出的 async/await 模式在 SE-0296 and SE-0303 等)。所有这些都是用于管理异步任务之间的依赖关系、控制并发度等的优雅解决方案

在我们提出任何具体建议之前,我们可能需要更好地了解您更广泛需求的性质。您已经问过有关单个调度的问题,但最好从您要实现的目标的更广泛背景下看待这个问题。例如,我假设您会问,因为您有多个异步请求要启动:您真的需要确保它们按顺序发生并失去并发的所有性能优势吗?或者您是否可以允许它们并发 运行 而您只需要知道所有并发请求何时完成以及如何以正确的顺序获得结果?并且您可能有很多并发请求,您可能需要限制并发度?

这些问题的答案可能会影响我们对如何最好地管理您的多个异步请求的建议。但答案几乎可以肯定不是GCD队列。