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

Why does DispatchSemaphore.wait() block this completion handler?

所以我一直在尝试使用 NetworkExtension 来制作玩具 VPN 实施,我 运行 遇到了完成 handlers/asynchronously 运行ning 代码的问题。我将 运行 带您完成我的 thought/expirments 训练,如果您能指出我错误的地方,以及如何解决这个问题,我将不胜感激!

这是最小的可重现代码(显然您需要 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”之前挂起。如果我删除 semaphore.wait() 行,打印输出将按预期顺序发生:1, 3, 2 (作为闭包 运行s 之后)。

所以在对调试器进行了一些研究之后,信号量陷阱循环似乎正在阻止执行。这激发了我对队列的了解,我发现将其更改为以下工作:

// ... as before
DispatchQueue.global().async {
    semaphore.wait()
    print("3 after")
}

这是有道理的,因为阻塞 .wait() 调用现在在单独的线程中异步调用。但是,这个解决方案对我来说并不理想,因为在我的实际实现中,我实际上是从闭包中捕获结果,然后 returning 它们,看起来像这样:

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

显然我不能 return 来自 async 闭包的数据,将 return 移出它会使它失效。此外,添加另一个信号量以使事情同步会出现与之前相同的问题,只是将问题移到链中。

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

// ...
DispatchQueue.global().async {
    NETunnelProviderManager.loadAllFromPreferences { loadedManagers, error in
        print("2 during")
        semaphore.signal()
    }
}
// ...

但是这不起作用并且 .wait() 调用从未通过 - 和以前一样。我假设 sempahore 仍然以某种方式阻塞线程并且不允许执行任何东西,这意味着系统中管理队列的任何东西都没有 运行 异步块?不过我是在求救,怕我原来的结论不对。

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

谢谢!

来自 NETunnelProviderManager loadAllFromPreferences 的文档:

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

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

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

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

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

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