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

Swift Concurrency 实现音频文件顺序处理时UI更新异常及同步执行问题求助

Swift Concurrency 实现音频文件顺序处理时UI更新异常及同步执行问题求助

兄弟,太懂你从PowerShell转Swift并发的那种懵圈感了!刚上手SwiftUI异步任务的时候,踩这种基础坑简直太正常了,我来帮你捋捋问题出在哪,再给你改改代码。

首先说你遇到的核心问题:UI只显示最后一个文件名、所有“转录任务”并行跑,根源出在你的processFiles方法里——你循环每个文件都开了一个独立的Task,这些任务会同时启动,相当于所有processFile几乎在同一时间设置fileName,最后一个文件的名字自然会覆盖前面所有的;同时所有模拟耗时的定时器也一起触发,控制台就会一堆输出挤在一起。

再说说你那个Task.synchronous扩展,这玩意儿完全违背了Swift并发的设计思路啊!它用信号量阻塞当前线程,配合DispatchQueue.main.asyncAfter很容易搞出死锁——比如你后来试的Task.sleep在这个同步方法里挂了,就是因为主线程被信号量堵死了,Task.sleep的异步逻辑根本没法执行,信号量永远收不到signal,直接卡死。

接下来给你改出正确的顺序执行版本:

第一步:让任务真正顺序执行

把processFiles改成异步函数,在同一个Task里顺序遍历文件,等待每个文件处理完成后再取下一个:

private func processFiles() async {
    for file in files {
        await processFile(file)
    }
}

然后在onAppear里用一个Task来调用它,别直接同步执行:

.onAppear {
    Task {
        files = await getFiles() // 顺便把文件读取也移到后台,避免卡UI
        await processFiles()
    }
}

第二步:正确模拟耗时任务

删掉那个坑爹的Task.synchronous扩展,用Swift并发原生的Task.sleep来模拟耗时,这才是符合Async/Await规范的写法:

private func processFile(_ url: URL) async {
    // 更新UI,SwiftUI会自动把@State的更新切回主线程
    fileName = url.lastPathComponent
    
    let seconds = Double.random(in: 10.0...20.0)
    let nanoseconds = UInt64(seconds * 1_000_000_000)
    
    do {
        // 模拟耗时任务,这里替换成真实的SFSpeech识别逻辑就行
        try await Task.sleep(nanoseconds: nanoseconds)
        print("\(url.lastPathComponent) 耗时 \(seconds) 秒完成")
    } catch {
        // 处理任务取消或者错误(比如用户中途退出)
        print("\(url.lastPathComponent) 处理失败:\(error.localizedDescription)")
    }
}

第三步:优化文件读取(可选但推荐)

原来的getFiles是同步方法,如果你的音频文件夹里文件很多,在onAppear里同步调用会短暂阻塞UI,把它改成异步方法移到后台执行:

private func getFiles() async -> [URL] {
    do {
        let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let path = documentsURL.appendingPathComponent("Voice Memos").absoluteURL
        
        let contents = try FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil, options: [])
        
        return contents
            .filter { $0.pathExtension == "m4a" }
            .sorted { $0.path > $1.path }
    } catch {
        print("读取文件失败:\(error.localizedDescription)")
        return []
    }
}

额外说下并行的问题

你提到SFSpeechURLRecognitionRequest不支持并行,那现在的顺序执行就是最稳妥的方案。如果以后官方开放了并行能力,或者你想自己控制并发数(比如同时处理2个文件),可以用TaskGroup的withLimitedConcurrency来实现,不过那是后话了,先把基础的顺序跑通再说~

备注:内容来源于stack exchange,提问作者Gordon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 08:48:09