问题描述
class SomeViewController: UIViewController {
let semaphore = DispatchSemaphore(value: 1)
deinit {
semaphore.signal() // just in case?
}
func someLongAsyncTask() {
semaphore.wait()
...
semaphore.signal() // called much later
}
}
如果我告诉信号量等待,然后在信号量被告知发出信号之前取消初始化拥有它的视图控制器,应用程序崩溃并出现Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
错误。但是,如果我只是在视图控制器的deinit
方法中调用semaphore.signal()
,灾难就避免了。然而,如果异步函数在deinit
被调用之前返回,并且视图控制器被取消初始化,则signal()
被调用两次,这似乎不是问题。但这样做安全和/或明智吗?
推荐答案
您在DispatchSemaphore
中遇到了一个功能/错误。如果查看堆栈跟踪并跳到堆栈的顶部,您将看到带有消息的程序集:
LIBDISPATCH客户端错误:信号量对象在使用时被释放
例如,
这是因为DispatchSemaphore
检查与信号量相关联的value
是否小于deinit
,如果是,则失败。简而言之,如果该值较小,则libDispatch得出结论,该信号量仍在使用中。
这可能看起来过于保守,因为这通常发生在客户端草率的情况下,而不是因为一定有一些严重的问题。如果它发出一个有意义的异常消息,而不是强迫我们挖掘堆栈跟踪,那就更好了。但这就是libDispatch的工作方式,我们必须接受它。
话虽如此,有三种可能的解决方案:
您显然有一条执行路径,在对象被释放之前,您正在执行而不是到达
signal
。更改代码,这样就不会发生这种情况,您的问题也就消失了。虽然您只需确保
wait
和signal
呼叫是均衡的(修复问题的根源),但您可以在您的问题中使用该方法(解决问题的症状)。但deinit
方法通过使用非局部推理解决了问题。如果您更改了初始化,例如,值为5,则您或某个未来的程序员必须记住也要转到deinit
并再插入四个signal
调用。另一种方法是使用零值实例化信号量,然后在初始化期间,只需
signal
足够的次数就可以使该值达到您想要的位置。那么你就不会有这个问题了。这将使问题的解决局限于初始化,而不是每次在初始化期间更改非零值时都必须记住调整deinit
。请参阅https://lists.apple.com/archives/cocoa-dev/2014/Apr/msg00483.html。
ITAI列举了一些根本不应该使用信号量的原因。还有很多其他原因:
- 信号量与新的SWIFT并发系统不兼容(请参阅Swift concurrency: Behind the scenes);
- 信号量如果在代码中不精确,也很容易导致死锁;
- 信号量通常与可取消的异步例程等相反。
如今,信号量几乎总是错误的解决方案。如果您告诉我们您正在尝试使用信号量解决什么问题,我们或许能够推荐其他更好的解决方案。
您说:
但是,如果异步函数在deinit
被调用之前返回,并且视图控制器被取消初始化,则signal()
被调用两次,这似乎不是问题。但这样做安全和/或明智吗?
从技术上讲,过度发送信号不会带来新的问题,因此您真的不必担心这一点。但这种"以防万一"的过度信号确实有一丝代码味道。它告诉您,在某些情况下,您正在等待但从未到达信令,这表明存在逻辑错误(请参阅上面的第1点)。
这篇关于在取消初始化之前发出信号量信号安全吗?以防万一?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!