Swift 为什么我们在异步函数中调用主线程上的完成?

Swift 为什么我们在异步函数中调用主线程上的完成?,swift,concurrency,Swift,Concurrency,例如在这个函数中 func asyncAdd(_ input: (Int, Int), runQueue: DispatchQueue = DispatchQueue.global(qos: .userInitiated), completionQueue: DispatchQueue = DispatchQueue.main, completion: @escaping (Result<Int, SlowAddError>) -> ()) { runQueue.async {

例如在这个函数中

func asyncAdd(_ input: (Int, Int),
runQueue: DispatchQueue = DispatchQueue.global(qos: .userInitiated),
completionQueue: DispatchQueue = DispatchQueue.main,
completion: @escaping (Result<Int, SlowAddError>) -> ()) {
runQueue.async {
    let result = input.0 + input.1
    completionQueue.async {
        completion(result)
    }
 }
}
func异步添加(uu输入:(Int,Int),
runQueue:DispatchQueue=DispatchQueue.global(qos:.userInitiated),
completionQueue:DispatchQueue=DispatchQueue.main,
完成:@转义(结果)->()){
runQueue.async{
让结果=输入。0+输入。1
completionQueue.async{
完成(结果)
}
}
}

为什么主线程completionQueue必须在调用完成的地方。我们不能只在后台线程上调用完成吗?

是的,但是在主队列上调用完成处理程序通常很方便,因为想要更新UI是很常见的,在错误的队列上调用UI更新程序是很常见的错误

对主队列进行通用异步函数回调不是必需的,甚至不是普遍推荐的。但是对于某些已知用例的系统,它非常方便,并且有助于防止bug

当然,上面的示例没有用处,在生产代码中很可能找不到。但在特殊情况下,这并不会改变效用。另一种常见模式是调用方传递完成队列。这也很好,特别是对于非常通用的工具(例如使用这种方法的URLSession)。

“我们能不能不在后台线程上调用完成?”

嗯,在一般情况下,如果这个后台线程是工作线程,我会投反对票。您必须根据具体情况决定是否可以安全地执行此操作:

异步执行任务的对象(让我们称之为“操作”)可能对执行任务的工作线程(或调度队列)有特殊要求。这可能包括特殊的服务质量级别(请参阅)、它是串行队列的事实等。工作线程位于“操作”内部,专门用于运行其任务,但可能不适合运行其他任务或函数

当操作在其工作线程上调用完成处理程序时,它不再控制其工作线程的情况。例如,客户端可以直接执行扩展的CPU密集型计算,从而暂停操作,因为它的工作线程现在很忙,必须等待客户端的完成处理程序完成。这些计算也可能在操作定义的QoS上运行,该操作根本不适合客户的要求

因此,这一切都可能给复杂的异步系统带来性能问题,甚至使其行为不可预测


例如URLSession,它不在其内部工作线程上调用完成处理程序,而是在专用的“回调”操作队列上调用完成处理程序。使用URLSession时,您可以提供它,或者URLSession将为您创建一个。

这取决于完成处理程序实际执行的操作。通常,在使用回调修改UI时会看到这种情况,在iOS中,只能从主线程进行修改