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

线程安全致TextProgress与TextMax不一致,寻求更优锁实现方案

解决多线程下ViewModel属性的线程安全问题

你的问题核心在于TextProgress++并非原子操作——它包含读取当前值、加1、写回新值三个独立步骤,多线程并发执行时会出现竞态条件,导致最终数值和预期的TextMax不符。不想锁定整个ViewModel、也不愿为每个属性单独建类的话,这里有几个更优雅的解决方案:

方案1:使用Interlocked类做原子操作

对于简单的整数自增/自减操作,.NET提供的System.Threading.Interlocked类能保证这些操作的原子性,无需显式加锁,性能也更优。

实现方式:

在ResultsItemViewModel中封装线程安全的增量方法,把原子操作的逻辑隐藏在内部:

public class ResultsItemViewModel : INotifyPropertyChanged
{
    int _textProgress, _textMax;
    // ... 原有属性和NotifyPropertyChanged代码保持不变 ...

    // 新增线程安全的增量方法
    public void IncrementTextProgress()
    {
        // 原子性地自增_textProgress,并获取新值
        int newValue = Interlocked.Increment(ref _textProgress);
        // WPF中需确保属性变更通知在UI线程触发
        Application.Current.Dispatcher.Invoke(() => 
            NotifyPropertyChanged(nameof(TextProgress)));
    }
}

然后在Method1和Method2中,替换原来的_ViewModel.TextProgress++为:

_ViewModel.IncrementTextProgress();

这样既避免了竞态条件,又不需要锁定整个ViewModel,粒度精准到单个属性的操作。

方案2:为单个属性分配专属锁对象

如果你的属性操作不止简单自增(比如需要做复杂的数值计算),可以为每个需要线程安全的属性单独创建一个私有的锁对象,锁的粒度足够细,不会影响其他属性的操作。

实现方式:

public class ResultsItemViewModel : INotifyPropertyChanged
{
    int _textProgress, _textMax;
    // 为TextProgress分配专属锁对象
    private readonly object _textProgressLock = new object();
    // ... 原有属性和NotifyPropertyChanged代码保持不变 ...

    public void IncrementTextProgress()
    {
        lock (_textProgressLock)
        {
            _textProgress++;
            NotifyPropertyChanged(nameof(TextProgress));
        }
    }
}

外部调用同样是_ViewModel.IncrementTextProgress(),这样锁只会作用于TextProgress相关的操作,其他属性的更新不受影响。

方案3:结合IProgress<T>做线程安全的进度报告

你原来的方法里有reportProgress参数,其实可以利用IProgress<T>来统一处理进度更新,它会自动把更新调度到UI线程(创建时捕获当前同步上下文),同时也能避免竞态条件:

实现方式:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    _ViewModel.TextProgress = 0;
    // 创建Progress对象,自动绑定UI线程上下文
    var progress = new Progress<int>(value => _ViewModel.TextProgress = value);
    await TextProcessing(progress);
    lblResult.Content = _ViewModel.TextProgress + "/" + _ViewModel.TextMax;
}

// 修改TextProcessing和Method1/Method2,传入进度实例
private async Task TextProcessing(IProgress<int> progress)
{
    int totalProgress = 0;
    await Task.WhenAll(
        Task.Run(() => Method1(val => Interlocked.Add(ref totalProgress, val))),
        Task.Run(() => Method2(val => Interlocked.Add(ref totalProgress, val))));
    // 最后统一更新UI进度
    progress.Report(totalProgress);
}

private void Method1(Action<int> reportProgress = null)
{
    for (int i = 0; i < 10000; i++)
    {
        reportProgress?.Invoke(1);
    }
}

// Method2做同样修改

这个方案更适合需要批量汇总多线程进度的场景,同时天然保证了UI线程的安全性。

选择建议

  • 如果只是简单的整数自增/自减,优先用方案1(Interlocked),性能最优且代码简洁;
  • 如果有复杂的属性操作(比如多步计算后更新),用方案2(专属锁对象),灵活且粒度可控;
  • 如果需要和UI更新强绑定,或者要汇总多个线程的进度,用**方案3(IProgress)**更合适。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:37:03