我在iOS上使用信号量时遇到了一个问题。我正在实现一个功能,以便按顺序执行一系列异步方法。
let semaphore = DispatchSemaphore(value: 1)
semaphore.wait()
performFirstTask {
semaphore.signal
}
semaphore.wait()
performSecondTask {
semaphore.signal
}
semaphore.wait()
performThirdTask {
semaphore.signal
}
因此,这是按预期工作的,但如果用户在等待状态下离开屏幕,就会出现问题,因此当特定任务的回调触发时,视图可能已经解除分配,从而导致崩溃,有人能帮我解决这个问题吗?我看不出有任何方法可以释放信号量。
提前感谢
这个基于信号量的代码应该退役。现在我们使用Swift并发的async
-await
。请参阅Swift中的WWDC 2021视频Meet async/await,以及该页面上引用的其他视频。
如果您出于某种原因没有考虑Swift并发(即,您需要支持不支持async
-await
的操作系统版本(,您可能会考虑Combine,或自定义异步Operation
子类,或许多第三方解决方案(例如,promise或futures(。但如今,信号量是一种反模式。
使用信号量有很多问题:
- 它效率低下(因为它不必要地占用了线程(
- 如果不小心,它会带来死锁风险
- 如果你在主线程上这样做,可能会导致不合格的用户体验和/或看门狗进程杀死你的应用程序
话虽如此,问题很可能是当信号量的值小于创建时的值时(例如,您创建的信号量值为1
,在释放时可能为0
(,信号量被释放。看见https://stackoverflow.com/a/70458886/1271826.
您可以通过从零开始来避免这个问题。要做到这一点,您需要:
-
删除第一个
wait
:let semaphore = DispatchSemaphore(value: 0) // not 1 // semaphore.wait() performFirstTask { semaphore.signal() } semaphore.wait() performSecondTask { semaphore.signal() } …
-
或者,如果你需要第一个
wait
,只需先做一个signal
:let semaphore = DispatchSemaphore(value: 0) // not 1 semaphore.signal() // now bump it up to 1 semaphore.wait() performFirstTask { semaphore.signal() } semaphore.wait() performSecondTask { semaphore.signal() } …
同样,您应该完全停止使用信号量,但如果必须,您可以使用这两种技术中的任何一种,以确保释放时的计数不小于初始化时的计数。
让我们想象一下,您决定采用Swift并发,而不是使用信号量。那么,async
-await
会是什么样子呢?
让我们想象一下,您重构了performFirstTask
、performSecondTask
和performThirdTask
以采用Swift并发。然后,您完全消除了信号量,您的15行代码被简化为:
Task {
await performFirstTask()
await performSecondTask()
await performThirdTask()
}
这将按顺序执行这三个异步任务,但避免了信号量的所有缺点。async
-await
的全部思想是,您可以非常优雅地表示一系列异步任务之间的依赖关系。
现在,您通常会重构performXXXTask
方法以采用Swift并发。或者,您也可以为它们编写async
"包装器"函数,例如:
func performFirstTask() async {
await withCheckedContinuation { continuation in
performFirstTask() {
continuation.resume(returning: ())
}
}
}
这是调用完成处理程序格式副本的performFirstTask
的async
格式副本。
无论您决定如何做(重构这三个方法,或者只为它们编写包装器(,Swift并发都大大简化了过程。请参阅WWDC 2021视频Swift并发:更新示例应用程序,了解更多关于如何转换遗留代码以采用Swift并发的示例。