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

Progress<T>类同步问题:进度报告顺序错乱如何解决?

问题分析与解决

问题代码

Task Foo(IProgress<int> onProgressPercentChanged){
    return Task.Run(() =>{
        for (int i = 0; i < 1000; i++){
            if (i % 10 == 0)
                onProgressPercentChanged.Report(i / 10);
            //Some operation
        }
    });
}

var progress = new Progress<int>(i => Console.WriteLine(i + " %"));
await Foo(progress);
Thread.Sleep(10000);

进度报告打印顺序混乱的核心原因是:Progress<T>的Report方法会将回调异步调度到创建Progress<T>时捕获的同步上下文(控制台程序无UI同步上下文,会 fallback 到线程池线程)。当循环执行速度远快于回调打印速度时,多个Report请求会被线程池的不同线程处理,线程调度的随机性直接导致打印顺序错乱。加Thread.Sleep(10)只是靠延迟“碰巧”让回调赶上下一次Report前执行,本质是用延迟掩盖线程竞争问题。

解决方法

方法1:给回调加锁,确保打印操作线程安全

直接在Progress<T>的回调逻辑中加锁,保证所有打印操作按顺序执行:

var lockObj = new object();
var progress = new Progress<int>(i => 
{
    lock(lockObj)
    {
        Console.WriteLine(i + " %");
    }
});
await Foo(progress);

这个方法改动最小,适合只需要保证输出顺序的场景。

方法2:实现同步版的IProgress<T>

如果不需要跨线程调度回调(比如控制台程序无需切换到UI线程),可以自己实现一个同步执行回调的IProgress<T>,从根源避免异步调度带来的顺序问题:

public class SyncProgress<T> : IProgress<T>
{
    private readonly Action<T> _handler;

    public SyncProgress(Action<T> handler)
    {
        _handler = handler ?? throw new ArgumentNullException(nameof(handler));
    }

    public void Report(T value)
    {
        _handler(value);
    }
}

使用时替换Progress<T>即可:

var progress = new SyncProgress<int>(i => Console.WriteLine(i + " %"));
await Foo(progress);

这种方式下,回调会在调用Report的线程(也就是Task.Run的工作线程)上同步执行,天然保证顺序。

方法3:控制Report的调度节奏(可选优化)

如果循环内的操作确实极快,还可以在Foo方法内控制Report的调用节奏,确保上一次回调完成后再发送下一次进度:

Task Foo(IProgress<int> onProgressPercentChanged)
{
    return Task.Run(async () =>
    {
        var lastCallbackTask = Task.CompletedTask;
        for (int i = 0; i < 1000; i++)
        {
            if (i % 10 == 0)
            {
                var currentProgress = i / 10;
                // 等待上一次回调执行完成
                await lastCallbackTask;
                // 调度下一次回调并保存任务引用
                lastCallbackTask = Task.Run(() => onProgressPercentChanged.Report(currentProgress));
            }
            //Some operation
        }
        // 等待最后一次回调完成
        await lastCallbackTask;
    });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 21:06:11