Swift基于actor/async/await实现仅留最新待执行任务串行队列咨询
问题分析
你写的实现离目标逻辑有几个明显的偏差,直接运行会出现不符合预期的行为:
- 返回值时序错误:创建完处理
Task后立刻读取latestResult返回,此时Task还没开始执行,拿到的永远是上一次任务的结果,不是本次请求对应的处理返回值。 - 重复创建任务的竞态:多个
analyseItem调用会同时在await task?.value处等待,等前一个任务执行完后,这些等待的调用会依次继续执行,重复创建多个新的处理任务,打破串行执行的规则。 - 丢弃逻辑不完整:没有区分「当前有任务正在执行」和「当前空闲」的状态,空闲状态下新入队的任务不需要等待,应该立刻启动;同时任务执行完成后没有清空待执行项的残留值,下次启动会误处理过期的旧任务。
符合需求的实现方案
你要做的是最经典的丢弃中间任务的串行执行器,核心规则是同一时间最多跑1个任务,执行期间新来的请求只留最新的一个待执行,中间的全部丢弃,当前任务跑完后如果有待执行项就接着跑最新的,没有就回到空闲。
修正后的代码如下:
// 替换成你自己的Item定义即可 protocol Item { func processItem() -> Bool } actor Worker { // 标记当前是否有正在运行的处理任务 private var currentRunningTask: Task<Void, Never>? // 存当前任务执行期间收到的最新待处理项,默认nil表示没有待执行任务 private var latestPendingItem: Item? @discardableResult func analyseItem(_ item: Item) async -> Bool? { // 新请求进来直接覆盖待处理项,之前没来得及跑的旧待处理项自动丢弃 latestPendingItem = item // 如果现在已经有任务在跑,等它跑完就行,不用重复启动任务 if let runningTask = currentRunningTask { await runningTask.value // 可自行扩展:判断当前item是否被覆盖,返回特定标识告知调用方任务被丢弃 return nil } // 没有正在跑的任务,启动执行循环 let processTask = Task { [weak self] in guard let self else { return } // 循环处理:只要还有待处理的最新项,就一直执行,直到没有待处理项为止 while let itemToProcess = self.latestPendingItem { // 取到待处理项立刻清空标记,执行过程中新来的请求会重新给latestPendingItem赋值 self.latestPendingItem = nil // 执行实际的处理逻辑 _ = itemToProcess.processItem() } // 所有待处理项执行完,清空运行标记,回到空闲状态 self.currentRunningTask = nil } currentRunningTask = processTask await processTask.value return nil } }
补充说明:如果你需要每个调用
analyseItem的地方都能拿到自己请求对应的处理结果(而不是被丢弃),需要给每个入队请求维护对应的CheckedContinuation,当待执行项被新请求覆盖时,提前结束旧请求的Continuation(比如返回nil或者自定义的任务被丢弃的错误),避免调用方无限挂起。
内容的提问来源于stack exchange,提问作者user149533
相关产品推荐
相关产品推荐

