You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从不同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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 19:24:58