Swift 为什么DispatchSemaphore.wait()会阻止此完成处理程序?

Swift 为什么DispatchSemaphore.wait()会阻止此完成处理程序?,swift,asynchronous,closures,grand-central-dispatch,dispatchsemaphore,Swift,Asynchronous,Closures,Grand Central Dispatch,Dispatchsemaphore,所以我一直在玩弄NetworkExtension来实现一个玩具VPN,我遇到了一个关于完成处理程序/异步运行代码的问题。我将向您介绍我的思路/期限,并希望您能指点我犯错误的地方,以及如何解决此问题 下面是最小的可复制代码位(显然您需要导入NetworkExtension): let信号量=调度信号量(值:0) NETunnelProviderManager.loadAllFromPreferences{managers,中出现错误 打印(“2期间”) 信号量 } 打印(“1之前”) 信号量。等待

所以我一直在玩弄NetworkExtension来实现一个玩具VPN,我遇到了一个关于完成处理程序/异步运行代码的问题。我将向您介绍我的思路/期限,并希望您能指点我犯错误的地方,以及如何解决此问题

下面是最小的可复制代码位(显然您需要
导入NetworkExtension
):

let信号量=调度信号量(值:0)
NETunnelProviderManager.loadAllFromPreferences{managers,中出现错误
打印(“2期间”)
信号量
}
打印(“1之前”)
信号量。等待()
打印(“3后”)
根据我对信号量和异步代码的理解,我希望打印输出按以下顺序进行:

1 before
2 during
3 after
但是,程序挂起在“1 before”。如果我删除
semaphore.wait()
行,打印输出按预期顺序出现:1、3、2(闭包稍后运行)

因此,在对调试器进行了一些深入研究之后,看起来信号量陷阱循环正在阻塞执行。这激发了我对队列的了解,我发现将其更改为以下内容非常有效:

/。。。一如既往
DispatchQueue.global().async{
信号量。等待()
打印(“3后”)
}
这有一定的意义,因为阻塞
.wait()
调用现在在一个单独的线程中异步调用。但是,我并不需要这个解决方案,因为在我的实际实现中,我实际上是从闭包中捕获结果并在稍后返回它们,如下所示:

let信号量=调度信号量(值:0)
var结果:[NETunnelProviderManager]?=无
NETunnelProviderManager.loadAllFromPreferences{managers,中出现错误
打印(“2期间”)
结果=经理
信号量
}
打印(“1之前”)
//DispatchQueue.global().async{
信号量。等待()
打印(“3后”)
// }
返回结果
显然,我无法从
async
闭包返回数据,如果将返回移出它,将使其失效。另外,添加另一个信号量以使事情同步会出现与之前相同的问题,只是将问题沿链移动

因此,我决定尝试将
.loadAllFromPreferences()
调用和完成处理程序放入
async
闭包中,并保留原始代码片段中的所有内容:

/。。。
DispatchQueue.global().async{
NETunnelProviderManager.loadAllFromPreferences{LoadedManager,中出现错误
打印(“2期间”)
信号量
}
}
// ...
但是,这不起作用,
.wait()
调用从未传递过-与以前一样。我假设sempahore仍然阻塞线程,不允许执行任何东西,这意味着系统中管理队列的任何东西都没有运行异步块?然而,我在这里抓住救命稻草,担心我最初的结论可能不正确

这就是我开始不了解的地方,所以我想知道实际发生了什么,以及您建议采用什么样的解决方案来同步从
.loadAllFromPreferences()
获得结果


谢谢

来自
NETunnelProviderManager loadAllFromPreferences
的文档:

加载操作完成后,此块将在调用方的主线程上执行

因此,我们知道完成处理程序位于主线程上

我们还知道,对
DispatchSemaphore wait
的调用将阻塞它正在运行的任何线程。有了这些证据,您必须从主线程调用所有这些代码。由于对
wait
的调用阻塞了主线程,因此无法调用完成处理程序,因为主线程被阻塞

您尝试在某个全局后台队列上调用
wait
,这就清楚了。这允许调用完成块,因为使用
wait
不再阻塞主线程

您尝试从全局后台队列调用
loadAllFromPreferences
不会改变任何事情,因为它的完成块仍然在主线程上被调用,而对
wait
的调用仍然在主线程上


完全阻塞主线程是个坏主意。正确的解决方案是重构此代码中的任何方法,以使用其自己的完成处理程序,而不是尝试使用正常的返回值。

异步等待分派信号量没有意义。信号灯的全部作用是暂停并等待某件事情完成。这样做会异步删除暂停,并随之删除信号量的整个点。