swift - 为什么 DispatchSemaphore.wait() 会阻止这个完成处理程序?
问题描述
所以我一直在玩 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()
以同步方式获得结果?
谢谢!
解决方案
从文档中NETunnelProviderManager loadAllFromPreferences
:
该块会在加载操作完成后在调用者的主线程上执行
所以我们知道完成处理程序在主线程上。
我们也知道调用DispatchSemaphore wait
将阻塞它正在运行的任何线程。鉴于此证据,您必须从主线程调用所有这些代码。由于您的调用wait
阻塞了主线程,因此永远无法调用完成处理程序,因为主线程被阻塞了。
您尝试调用wait
一些全局后台队列时可以清楚地说明这一点。这允许调用完成块,因为您的使用wait
不再阻塞主线程。
而且您尝试loadAllFromPreferences
从全局后台队列调用不会改变任何事情,因为它的完成块仍然在主线程上调用,并且您的调用wait
仍然在主线程上。
完全阻塞主线程是个坏主意。正确的解决方案是重构此代码所在的任何方法以使用其自己的完成处理程序,而不是尝试使用正常的返回值。
推荐阅读
- python - 来自熊猫数据框的散点图上的 Matplotlib 图例
- php - IIS SERVER curl_setopt_array():无法创建临时文件
- php - 如何使用 f3 beforeroute() 检查登录用户,重定向到登录然后返回原始路由?
- python - 在 Maya 的 liveMode 中从用户启动的 QuadDraw 工具中查询一个值
- c++ - 使用 GTK 将 EGL 图像渲染到帧缓冲区
- npm - 如何检查 NPM 包名称是否可用?
- azure - 运行由 VS2019 创建的 Azure Docker 容器注册表
- google-api - GMail API 用户速率在较长时间内受到限制
- python - 为什么 cf.go_offline 在 pyCharm 中不起作用
- java - org.springframework.data.domain.PageImpl 不能转换为