detached任务中调用MainActor.run无法执行闭包的问题如何解决
问题根源
你遇到的问题核心是MainActor.run和DispatchQueue.main.async的行为差异,以及Swift Concurrency的协作式执行特性:
DispatchQueue.main.async是纯异步提交操作:把任务扔到主队列后立刻返回,后台全局队列的任务会继续执行,不需要等待主队列执行完提交的闭包。await MainActor.run是同步等待操作:提交闭包到主Actor后,后台任务会挂起,一直等到主Actor把这个闭包执行完成,才会恢复执行后续的统计逻辑。
当你统计大量文件时,短时间内会频繁向主Actor提交更新任务,主队列会被大量的属性更新任务占满,UI渲染任务(比如刷新Text的显示、响应用户操作)无法得到调度,就会出现卡顿、风火轮、UI不更新的问题。你加的Task.sleep相当于强制让后台任务挂起1秒,给主Actor留出时间处理队列里的已有任务,所以能临时解决问题,但属于不符合逻辑的 workaround。
修复方案
把await MainActor.run替换为主动创建主Actor任务的写法,实现和DispatchQueue.main.async完全一致的异步提交效果,提交完更新任务后后台统计立刻继续执行,不需要等待主Actor处理完成:
final class NewFileCounter: ObservableObject { @Published var fileCount = 0 func findImagesInFolder(_ folderURL: URL) { let fileManager = FileManager.default Task.detached(priority: .userInitiated) { // 对齐旧代码的QoS优先级 var foundFileCount = 0 let options = FileManager.DirectoryEnumerationOptions(arrayLiteral: [.skipsHiddenFiles, .skipsPackageDescendants]) if let enumerator = fileManager.enumerator(at: folderURL, includingPropertiesForKeys: [], options: options) { while let _ = enumerator.nextObject() as? URL { foundFileCount += 1 if foundFileCount % 10_000 == 0 { let fileCount = foundFileCount // 异步提交主Actor更新,不等待执行完成 Task { @MainActor in self.fileCount = fileCount } } } Task { @MainActor in self.fileCount = foundFileCount } } } } }
如果文件量特别大,还可以进一步降低更新频率(比如调整为每5万/10万个文件更新一次),或者增加时间间隔判断,避免短时间内向主队列提交过多更新任务,进一步提升流畅度。
内容的提问来源于stack exchange,提问作者Philip Pegden
相关产品推荐
相关产品推荐

