swift - 为什么 DispatchSemaphore.wait() 会阻止这个完成处理程序?

标签 swift asynchronous closures grand-central-dispatch dispatchsemaphore

所以我一直在尝试使用 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() 调用从未通过 - 和以前一样。我假设 sempahore 仍然以某种方式阻塞线程并且不允许执行任何操作,这意味着系统中管理队列的任何内容都没有运行异步 block ?但是我在这里抓着救命稻草,担心我最初的结论可能不正确。

这是我开始超出我的深度的地方,所以我想知道实际发生了什么,以及您建议使用什么分辨率来从 .loadAllFromPreferences()以同步方式?

谢谢!

最佳答案

来自 NETunnelProviderManager loadAllFromPreferences 的文档:

This block will be executed on the caller’s main thread after the load operation is complete

所以我们知道完成处理程序在主线程上。

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

您尝试在某些全局后台队列上调用 wait 可以清楚地表明这一点。这允许调用完成 block ,因为您对 wait 的使用不再阻塞主线程。

并且您尝试从全局后台队列调用 loadAllFromPreferences 不会改变任何东西,因为它的完成 block 仍在主线程上调用并且您对 wait 的调用是仍在主线程上。

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

关于swift - 为什么 DispatchSemaphore.wait() 会阻止这个完成处理程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57946169/

相关文章:

ios - Swift - 基于键的映射字典

Swift:带有可选链接和 nil-coalescing 的编译时错误

php - 我可以在插入数据库值后立即使用它吗?

ios - 将 Swift 闭包作为值添加到 Swift 字典

iOS:当响应者辞职时,文本从子类 UITextfield 中消失

ios - ARKit 从屏幕平移创建节点

javascript - 如何使数组过滤器与异步函数端一起工作?

multithreading - 在 Scala Actor 中集成自主和 react 行为的最佳方式?

javascript - 在javascript中循环实例化类使用最后一个值