【问题标题】:Why does DispatchSemaphore.wait() block this completion handler?为什么 DispatchSemaphore.wait() 会阻止这个完成处理程序?
【发布时间】:2020-01-16 15:51:10
【问题描述】:

所以我一直在使用 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 闭包中,并将其他所有内容保留在原始代码 sn-p 中:

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

但是这不起作用,.wait() 调用从未通过 - 和以前一样。我假设信号量仍然以某种方式阻塞线程并且不允许执行任何操作,这意味着系统中管理队列的任何内容都没有运行异步块?但是,我在这里抓着稻草,担心我最初的结论可能不正确。

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

谢谢!

【问题讨论】:

  • 异步等待调度信号量没有意义。信号量的全部意义在于暂停并等待某事完成。这样做会异步删除暂停,并随之删除信号量的整个点。

标签: swift asynchronous closures grand-central-dispatch dispatchsemaphore


【解决方案1】:

来自NETunnelProviderManager loadAllFromPreferences 的文档:

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-23
    • 1970-01-01
    • 1970-01-01
    • 2014-01-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多