所以我一直在玩 NetworkExtension 来实现玩具 VPN,但我遇到了完成处理程序/异步运行代码的问题。我将引导您完成我的思路/实验,并感谢您在我犯错的领域以及如何解决此问题的任何指示!
这是最小的可重现代码(显然你需要import NetworkExtension
):
let semaphore = DispatchSemaphore(value: 0)
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
semaphore.signal()
}
print("1 before")
semaphore.wait()
print("3 after")
根据我对信号量和异步代码的理解,我希望打印输出按顺序发生:
1 before
2 during
3 after
但是程序挂在"1 before"。如果我删除该semaphore.wait()
行,打印输出按预期的顺序发生:1、3、2 (因为闭包稍后运行)。
因此,在使用调试器进行一些挖掘之后,看起来信号量陷阱循环正在阻塞执行。这促使我阅读了一些队列,我发现将其更改为以下工作:
// ... as before
DispatchQueue.global().async {
semaphore.wait()
print("3 after")
}
这是有道理的,因为阻塞.wait()
调用现在在单独的线程中被异步调用。但是,这个解决方案对我来说并不是我想要的,因为在我的实际实现中,我实际上是从闭包中捕获结果并稍后返回它们,看起来像这样:
let semaphore = DispatchSemaphore(value: 0)
var results: [NETunnelProviderManager]? = nil
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
results = managers
semaphore.signal()
}
print("1 before")
// DispatchQueue.global().async {
semaphore.wait()
print("3 after")
// }
return results
显然我不能从async
闭包中返回数据,并且将返回移出它会使它失效。此外,添加另一个信号量以使事情同步表现出与之前在链中移动问题相同的问题。
因此,我决定尝试将.loadAllFromPreferences()
调用和完成处理程序放在一个async
闭包中,并将其他所有内容保留在原始代码片段中:
// ...
DispatchQueue.global().async {
NETunnelProviderManager.loadAllFromPreferences { loadedManagers, error in
print("2 during")
semaphore.signal()
}
}
// ...
但是,这不起作用,并且.wait()
调用从未通过 - 和以前一样。我假设信号量仍然以某种方式阻塞线程并且不允许执行任何操作,这意味着系统中管理队列的任何内容都没有运行异步块?然而,我在这里抓着稻草,担心我最初的结论可能不正确。
这是我开始超出我的深度的地方,所以我想知道实际发生了什么,以及你建议什么分辨率.loadAllFromPreferences()
以同步方式获得结果?
谢谢!