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

Swift中实现线程安全进度报告的合理方案咨询

Swift多并发场景下ProgressGroup的线程安全实现方案

问题背景

我在Swift代码库中使用Progress和ProgressGroup类跟踪长时操作的进度:

  • ProgressGroup包含多个子Progress对象,子Progress会被传入长时执行函数,调用setProgress(_:)即可更新自身进度。
  • 子进度更新后会触发ProgressGroup的setChildProgress(_:progress:)方法,计算总进度(比如3个子进度为0.0、0.5、0.0时,总进度为0.16)。
  • ProgressGroup标记为@Observable,用于关联UI或其他跟踪模块。

原代码通过@MainActor隔离避免并发调用问题,但现在需要让Progress被不同actor的多并发任务调用:

  • 移除@MainActor隔离会引发竞态问题;
  • 使用Mutex不符合Swift推荐的并发范式;
  • 将ProgressGroup改为actor会产生大量actor实例,还会迫使整个调用链异步。

原代码示例:

@MainActor
@Observable
class ProgressGroup {
    var childProgress: [Float]
    var children: [Progress]! = []

    var totalProgress: Float = 0.0

    init (numChildren: Int) {
        self.childProgress = Array(repeating: 0.0, count: numChildren)
        self.children = (0..<numChildren).map { idx in Progress(group: self, index: idx) }
    }

    func setChildProgress(idx: Int, progress: Float) {
        self.childProgress[idx] = progress
        var total: Float = 0.0
        for childProgress in self.childProgress {
            total += childProgress
        }
        self.totalProgress = total / Float(self.children.count)  // triggers observers
    }
}

@MainActor
class Progress {
    let group: ProgressGroup
    let index: Int

    init (group: ProgressGroup, index: Int) {
        self.group = group
        self.index = index
    }

    func setProgress(progress: Float) {
        self.group.setChildProgress(idx: self.index, progress: progress)
    }
}

可行解决方案

方案1:自定义串行执行器(SerialExecutor)

给ProgressGroup指定专用的串行执行器,替代主actor,既保证线程安全,又不会阻塞主线程,也无需将整个类改为actor。

代码示例:

import Foundation
import Observation

// 定义专用串行执行器
private let progressGroupExecutor = SerialExecutor { task in
    let queue = DispatchQueue(label: "com.yourdomain.progressgroup")
    queue.async(execute: task)
}

@Observable
class ProgressGroup {
    var childProgress: [Float]
    var children: [Progress]! = []
    var totalProgress: Float = 0.0
    
    // 指定执行器
    nonisolated let executor: SerialExecutor = progressGroupExecutor
    
    init(numChildren: Int) {
        self.childProgress = Array(repeating: 0.0, count: numChildren)
        self.children = (0..<numChildren).map { idx in Progress(group: self, index: idx) }
    }
    
    func setChildProgress(idx: Int, progress: Float) {
        guard idx >= 0, idx < childProgress.count else { return }
        childProgress[idx] = progress
        let total = childProgress.reduce(0.0, +)
        totalProgress = total / Float(childProgress.count)
    }
}

class Progress {
    let group: ProgressGroup
    let index: Int
    
    init(group: ProgressGroup, index: Int) {
        self.group = group
        self.index = index
    }
    
    nonisolated func setProgress(progress: Float) {
        // 自动切换到ProgressGroup的串行执行器执行更新
        Task { [group, index] in
            await group.setChildProgress(idx: index, progress: progress)
        }
    }
}

说明:所有对setChildProgress的调用都会在专用串行队列上执行,避免竞态。Progress可在任意actor调用setProgress,内部通过Task自动切换执行上下文,无需调用方强制等待。


方案2:用ActorIsolated封装状态

将ProgressGroup的核心状态(子进度数组、总进度)用ActorIsolated封装,通过非隔离方法暴露更新接口,既保证线程安全,又无需手动管理actor。

代码示例:

import Foundation
import Observation

@Observable
class ProgressGroup {
    // 用ActorIsolated封装状态,自动保证线程安全
    private let _childProgress: ActorIsolated<[Float]>
    private let _totalProgress: ActorIsolated<Float>
    var children: [Progress]! = []
    
    // 对外暴露的总进度属性,自动同步内部隔离状态
    var totalProgress: Float {
        get { Task { await _totalProgress.value }.value }
        set { Task { await _totalProgress.setValue(newValue) }.value }
    }
    
    init(numChildren: Int) {
        self._childProgress = ActorIsolated(Array(repeating: 0.0, count: numChildren))
        self._totalProgress = ActorIsolated(0.0)
        self.children = (0..<numChildren).map { idx in Progress(group: self, index: idx) }
    }
    
    nonisolated func setChildProgress(idx: Int, progress: Float) {
        Task {
            await _childProgress.withValue { childProgress in
                guard idx >= 0, idx < childProgress.count else { return }
                childProgress[idx] = progress
                let total = childProgress.reduce(0.0, +)
                await _totalProgress.setValue(total / Float(childProgress.count))
            }
        }
    }
}

class Progress {
    let group: ProgressGroup
    let index: Int
    
    init(group: ProgressGroup, index: Int) {
        self.group = group
        self.index = index
    }
    
    nonisolated func setProgress(progress: Float) {
        group.setChildProgress(idx: index, progress: progress)
    }
}

说明:ActorIsolated会自动用隐藏的actor管理状态,所有状态修改都在隔离环境中执行,外部调用无需处理异步逻辑,@Observable能正常感知totalProgress的变化。


方案3:GCD串行队列保护状态

如果习惯传统GCD同步方式,用串行队列封装状态访问也是官方认可的方案,适合对actor模型不熟悉的场景。

代码示例:

import Foundation
import Observation

@Observable
class ProgressGroup {
    // 专用串行队列,保护状态访问
    private let queue = DispatchQueue(label: "com.yourdomain.progressgroup")
    private var _childProgress: [Float]
    private var _totalProgress: Float = 0.0
    var children: [Progress]! = []
    
    // 对外暴露的总进度,通过队列同步获取最新值
    var totalProgress: Float {
        queue.sync { _totalProgress }
    }
    
    init(numChildren: Int) {
        self._childProgress = Array(repeating: 0.0, count: numChildren)
        self.children = (0..<numChildren).map { idx in Progress(group: self, index: idx) }
    }
    
    func setChildProgress(idx: Int, progress: Float) {
        queue.async { [weak self] in
            guard let self = self, idx >= 0, idx < self._childProgress.count else { return }
            self._childProgress[idx] = progress
            let total = self._childProgress.reduce(0.0, +)
            self._totalProgress = total / Float(self._childProgress.count)
            
            // @Observable属性需在MainActor更新以触发UI观察
            Task { @MainActor in
                self.totalProgress = self._totalProgress
            }
        }
    }
}

class Progress {
    let group: ProgressGroup
    let index: Int
    
    init(group: ProgressGroup, index: Int) {
        self.group = group
        self.index = index
    }
    
    func setProgress(progress: Float) {
        group.setChildProgress(idx: index, progress: progress)
    }
}

说明:所有状态修改都在串行队列中执行,避免竞态。由于@Observable的UI观察依赖MainActor,最后通过@MainActor的Task更新对外暴露的totalProgress属性。

方案选择建议

  • 若追求Swift现代并发范式,优先选方案1或方案2:方案1的串行执行器更灵活,可自定义执行队列;方案2用ActorIsolated封装更简洁,无需手动管理执行上下文。
  • 若习惯GCD同步方式,可选用方案3,兼容性好,学习成本低。

内容的提问来源于stack exchange,提问作者tmlen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 20:54:53