从不同Task修改Swift Actor属性时其他Task无法感知变更的问题
问题分析与解决方案
你遇到的核心问题并非Actor属性无法跨Task感知,而是轮询式的sleep导致状态变更的响应延迟,同时这种轮询方式会造成不必要的资源消耗。Actor的属性访问是原子性的,每次await都会获取最新的状态,你看到的"始终为true"其实是sleep的1秒延迟还没结束,循环尚未执行到下一次状态检查。
下面是优化后的实现方案,去掉轮询sleep,改用事件驱动的方式处理状态变更:
1. 重构Actor:添加事件通知机制
通过CheckedContinuation实现状态变更时的主动唤醒,避免无意义的轮询:
fileprivate let MAX_THREADS = 5 actor ConverterData { private var threads: Int = 0 var aiff: [URL] = [] var isRunning: Bool = false var totalMp3Converted: Int = 0 private var continuations: [CheckedContinuation<Void, Never>] = [] // 线程数操作 func incThreads() { threads += 1 } func decThreads() { threads -= 1 notifyWaiters() } // 状态控制 func setIsRunning(_ flag: Bool) { isRunning = flag notifyWaiters() } // 任务队列操作 func addAiffUrl(_ url: URL) { aiff.append(url) notifyWaiters() } func incrementConvertedCount() { totalMp3Converted += 1 notifyWaiters() } // 取出下一个待转换文件 func takeNextAiffUrl() async -> URL? { guard !aiff.isEmpty else { return nil } return aiff.removeFirst() } // 判断是否可以启动新转换任务 func canStartNewTask() async -> Bool { return isRunning && !aiff.isEmpty && threads < MAX_THREADS } // 等待事件触发(新文件、线程释放、停止信号) func waitForEvent() async { if isRunning && !aiff.isEmpty && threads < MAX_THREADS { return } await withCheckedContinuation { continuation in continuations.append(continuation) } } // 唤醒所有等待的任务 private func notifyWaiters() { let pendingContinuations = continuations continuations.removeAll() pendingContinuations.forEach { $0.resume() } } }
2. 重构Converter类:事件驱动的任务调度
去掉sleep轮询,改为等待Actor的事件通知,实现高效的任务调度:
class Converter { private let converterData = ConverterData() func start() async { await converterData.setIsRunning(true) defer { Task { await converterData.setIsRunning(false) } } while await converterData.isRunning { // 等待事件触发,无事件则挂起,不消耗资源 await converterData.waitForEvent() // 批量启动可执行的转换任务 while await converterData.canStartNewTask() { await converterData.incThreads() guard let url = await converterData.takeNextAiffUrl() else { await converterData.decThreads() break } // 启动异步转换任务 Task { [weak self] in defer { Task { await self?.converterData.decThreads() await self?.converterData.incrementConvertedCount() } } await convertAiffToMp3(url: url) } } // 检查所有任务是否完成,完成则退出循环 let allTasksCompleted = await converterData.aiff.isEmpty && converterData.threads == 0 if allTasksCompleted { break } } } func stop() async { await converterData.setIsRunning(false) } // 模拟AIFF转MP3的实际逻辑 private func convertAiffToMp3(url: URL) async { // 替换为实际的转换代码 try? await Task.sleep(nanoseconds: 2_000_000_000) print("转换完成:\(url.lastPathComponent)") } }
3. 优化UI交互代码
保持原有结构,确保异步调用的正确性:
struct ContentView: View { private let converter = Converter() var body: some View { VStack(spacing: 20) { Button("Start") { Task { await converter.start() print("转换任务已结束") } } .buttonStyle(.borderedProminent) Button("Stop") { Task { await converter.stop() } } .buttonStyle(.bordered) .tint(.red) } .padding() } }
关键说明
- 无轮询资源消耗:只有当状态变化(添加新文件、线程释放、停止指令)时,才会唤醒等待的任务,避免了sleep轮询的资源浪费。
- 即时响应状态变更:点击Stop后,
setIsRunning(false)会立即唤醒等待的循环,无需等待sleep结束。 - Actor状态安全:所有对共享状态的操作都在Actor内部完成,确保线程安全。
内容的提问来源于stack exchange,提问作者Altair Jones
相关产品推荐
相关产品推荐

