iprogress 是进度值传递契约而非通知机制,其回调默认在 report() 调用线程执行,跨线程需手动同步;类型选择影响语义(int 适合离散步数,double 适合连续百分比);必须配合 cancellationtoken 实现安全取消;需主动管理生命周期,避免空引用和内存泄漏。

IProgressProgress<t></t> 实例,只负责把 Report() 调用原样转发给构造时绑定的回调(或事件),至于这个回调在哪个线程执行、是否安全访问 UI 控件,全由你控制。
Progress 的回调默认在调用 Report() 的线程上执行
这是最容易踩坑的地方:如果你在 Task.Run 里调用 progress.Report(50),而 Progress<t></t> 是在 UI 线程创建的,那回调(比如更新 ProgressBar.Value)就会在后台线程执行 → 直接抛 InvalidOperationException:“线程间操作无效”。
解决方法只有两种:
- 显式指定同步上下文:用
Progress<t>(action)</t>构造时传入 UI 线程的SynchronizationContext,但 WinForms/WPF 已自动捕获,通常不用手动做 - 确保
Report()只在 UI 线程调用:把耗时逻辑和Report()拆开,用await+Invoke或Dispatcher.Invoke包裹回调逻辑(不推荐) - 更稳妥的做法:在
Progress<t></t>构造后,直接用ProgressChanged事件绑定 UI 更新逻辑,系统会自动封送到 UI 线程(WinForms/WPF 默认行为)
IProgress 和 IProgress 的语义差异
别只看类型,要看业务含义:
-
IProgress<int></int>常用于离散步骤(如“第 3/10 步”),Report(3)表示当前完成第 3 步,Maximum需外部约定(比如 10) -
IProgress<double></double>更适合连续百分比(0.0–100.0),Report(42.5)可直接赋给ProgressBar.Value(只要Maximum == 100) - 混用会导致精度丢失或 UI 显示错乱:比如用
int报告 0–100 之间的值,但实际步长是 3,最后一步可能卡在 99 不到 100
Task.Run 里 Report 进度必须配合 CancellationToken
单纯用 Task.Run(() => { /* loop + Report */ }) 是危险的:用户点“取消”时,后台线程还在跑,Report() 可能继续发,UI 可能收到过期进度甚至崩溃。
正确姿势是把 CancellationToken 和 IProgress<t></t> 一起传入:
static async Task DoWorkAsync(CancellationToken token, IProgress<double> progress)
{
double total = 100;
for (int i = 0; i <p>注意:<code>Task.Delay(50, token)</code> 比 <code>Thread.Sleep(50)</code> 安全得多,前者可被取消且不阻塞线程。</p>
<h3>Progress<t> 的生命周期管理容易被忽略</t>
</h3>
<p><code>Progress<t></t></code> 实例不是一次性的,它持有对回调的引用。如果回调里捕获了窗体实例(比如 <code>this</code>),而窗体已关闭,但后台任务还在运行并持续 <code>Report()</code>,就会造成内存泄漏 + 空引用异常。</p>
<p>务必在任务结束或窗体关闭时做清理:</p>
<ul>
<li>WinForms:在窗体 <code>FormClosed</code> 事件中,设 <code>progress = null</code>,并在 <code>Report()</code> 前加空检查</li>
<li>更健壮做法:用弱引用包装回调,或改用 <code>async void</code> 事件处理(仅限 UI 层)</li>
<li>不要依赖 GC —— <code>Progress<t></t></code> 不实现 <code>IDisposable</code>,也没必要 Dispose</li>
</ul>
<p>最常被跳过的细节是:没人检查 <code>progress</code> 是否为 <code>null</code> 就直接 <code>Report()</code>,尤其在异步任务被快速启停的场景下,空引用异常几乎必然发生。</p></double>











