iOS Swift 中的信号量

3

我在iOS上使用信号量时遇到了问题。 我正在实现一个功能,按顺序依次执行一系列异步方法。

let semaphore = DispatchSemaphore(value: 1)
semaphore.wait()
performFirstTask {
   semaphore.signal
}

semaphore.wait()
performSecondTask {
   semaphore.signal
}

semaphore.wait()
performThirdTask {
   semaphore.signal
}

所以现在它正在按预期工作,但如果用户在等待状态下离开屏幕,那么当来自特定任务的回调触发时,视图可能已经被释放,这会导致崩溃。

请问是否有人能帮我解决这个问题?我没有找到任何释放信号量的方法。

提前感谢。


10
不要使用信号量。 - matt
2
请使用OperationQueue代替。如果适用的话,也可以使用async/await。 - timbre timbre
3
您需要将最大并发设置为1。 - matt
3
为什么不直接使用DispatchQueue呢?它们默认情况下是串行的(当你从头开始创建它们时)。 - matt
3
转换到 async await,这个东西过于复杂且容易出错。https://developer.apple.com/wwdc21/10132 - lorem ipsum
显示剩余4条评论
2个回答

6

这个基于信号量的代码应该被淘汰了,现在我们会使用Swift并发编程中的async-await。请参阅WWDC 2021视频“在Swift中遇见async/await”, 以及该页面引用的其他视频。

如果由于某种原因您没有考虑使用Swift并发(例如,您需要支持不支持async-await的操作系统版本),您可以考虑使用Combine,或自定义异步Operation子类,或许多第三方解决方案(例如承诺或期货)。但现在,信号量是一个反模式。

使用信号量存在一些问题:

  • 它效率低下(因为它不必要地占用了一个线程);
  • 如果不小心,会引入死锁风险;
  • 如果在主线程上执行此操作,则可能导致低劣的UX和/或看门狗进程杀死您的应用程序。

话虽如此,问题可能是当信号量的值小于创建时的值时(例如,您将其创建为1,但在释放时可能已经是0),它被释放了。请参见https://dev59.com/FlEG5IYBdhLWcg3wNWxs#70458886

您可以通过从零开始设置值来避免此问题。要做到这一点,您需要:

  1. Remove the first wait:

    let semaphore = DispatchSemaphore(value: 0)   // not 1
    
    // semaphore.wait()
    performFirstTask {
       semaphore.signal()
    }
    
    semaphore.wait()
    performSecondTask {
       semaphore.signal()
    }
    
    
    
  2. Or if you need that first wait, just do a preemptive 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()
    }
    
    
    

再次强调,您应该完全停止使用信号量,但如果必须使用,可以使用这两种技术之一来确保在释放计数时不会少于初始化时的计数。


4

让我们假设您决定采用Swift并发,而不是使用信号量。那么,使用async-await会是什么样子呢?

让我们再想象一下,如果您重构了performFirstTaskperformSecondTaskperformThirdTask以采用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的异步实现,它调用了完成处理程序实现。

然而,无论您如何决定(重构这三个方法或只编写其包装器),Swift并发都大大简化了该过程。查看WWDC 2021视频Swift concurrency: Update a sample app,了解有关如何将传统代码转换为使用Swift并发的更多示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接