WPF 异步编程的完整对比与实战总结,主要聚焦在 WPF 项目中最常用的几种异步模式,以及它们在工业上位机(HMI/SCADA)场景下的适用性、优缺点、性能表现
·
以下是对 WPF 异步编程 的完整对比与实战总结,主要聚焦在 WPF 项目中最常用的几种异步模式,以及它们在工业上位机(HMI/SCADA)场景下的适用性、优缺点、性能表现、代码复杂度、异常处理难度等维度。
一、WPF 中最常用的 5 种异步编程方式对比
| 方式 | 核心机制 | UI线程安全 | 代码复杂度 | 取消支持 | 进度报告 | 异常处理 | 适用场景(工控) | 推荐指数(2025) | 备注与坑点 |
|---|---|---|---|---|---|---|---|---|---|
| BackgroundWorker | 事件驱动(DoWork/ProgressChanged/RunWorkerCompleted) | 是(自动Marshal) | 低 | 支持 | 支持 | 中等 | 老项目、简单采集、进度条任务 | ★★☆☆☆ | 已过时,不推荐新项目,代码分散 |
| Task + Invoke / Dispatcher.Invoke | Task.Run + 手动回UI线程 | 需手动 | 中 | 支持 | 需手动 | 中等 | 过渡项目、混合老新代码 | ★★★☆☆ | 容易忘 Invoke 导致跨线程异常 |
| async/await + IProgress | 现代 async/await + 进度接口 | 是(自动) | 中低 | 支持 | 优秀 | 优秀 | 绝大多数新项目、采集、通信、文件操作 | ★★★★★ | 主流推荐,写法最优雅 |
| async/await + Dispatcher.Invoke | async/await + 手动回UI | 需手动 | 中 | 支持 | 需手动 | 优秀 | 需要精细控制UI更新的场景 | ★★★★☆ | 比 IProgress 更灵活,但容易写错 |
| Reactive Extensions (Rx) / CommunityToolkit.Mvvm | 响应式 + Observable | 是 | 高 | 优秀 | 优秀 | 优秀 | 高并发、多数据源、复杂事件流 | ★★★★☆ | 学习曲线陡,适合大型复杂HMI |
结论(2025 年 WPF 工业上位机推荐优先级):
- 首选:
async/await + IProgress<T>(最平衡、最现代、最易维护) - 次选:
async/await + Dispatcher.Invoke(需要精细控制时) - 复杂场景:Rx 或 CommunityToolkit.Mvvm 的 Observable(多路传感器、实时流)
- 坚决避免:BackgroundWorker(已过时)、纯 Task.ContinueWith(极易出错)
二、代码对比示例(同一个功能:异步读取PLC + 显示进度 + 更新曲线)
示例场景
- 每 200ms 读取一次 PLC 的 10 个寄存器(模拟耗时 150ms)
- 显示读取进度(0% → 100%)
- 把值追加到 ZedGraph 实时曲线
- 支持取消操作
1. BackgroundWorker 写法(老派,容易理解但不推荐)
private BackgroundWorker _worker;
private void btnStart_Click(object sender, EventArgs e)
{
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += (s, e) =>
{
for (int i = 0; i < 100; i++)
{
if (_worker.CancellationPending)
{
e.Cancel = true;
return;
}
// 模拟耗时PLC读取
Thread.Sleep(150);
// 读取值(实际替换为 plc.Read)
float value = 20 + (float)Math.Sin(i * 0.1) * 30;
_worker.ReportProgress(i + 1, value);
}
};
_worker.ProgressChanged += (s, e) =>
{
progressBar.Value = e.ProgressPercentage;
lblStatus.Text = $"进度:{e.ProgressPercentage}%";
// 更新曲线(安全)
chart.Series[0].Points.AddXY(e.ProgressPercentage, (float)e.UserState);
};
_worker.RunWorkerCompleted += (s, e) =>
{
if (e.Cancelled)
MessageBox.Show("已取消");
else if (e.Error != null)
MessageBox.Show("错误:" + e.Error.Message);
else
MessageBox.Show("完成");
};
_worker.RunWorkerAsync();
}
private void btnCancel_Click(object sender, EventArgs e)
{
_worker?.CancelAsync();
}
2. async/await + IProgress(现代推荐写法)
private CancellationTokenSource _cts;
private async void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
btnCancel.Enabled = true;
_cts = new CancellationTokenSource();
var progress = new Progress<(int percent, float value)>(data =>
{
// 此回调自动在 UI 线程执行
progressBar.Value = data.percent;
lblStatus.Text = $"进度:{data.percent}% 值:{data.value:F2}";
chart.Series[0].Points.AddXY(data.percent, data.value);
});
try
{
await ReadPlcLoopAsync(progress, _cts.Token);
MessageBox.Show("采集完成");
}
catch (OperationCanceledException)
{
MessageBox.Show("已取消");
}
catch (Exception ex)
{
MessageBox.Show("错误:" + ex.Message);
}
finally
{
btnStart.Enabled = true;
btnCancel.Enabled = false;
}
}
private async Task ReadPlcLoopAsync(IProgress<(int, float)> progress, CancellationToken ct)
{
for (int i = 0; i < 100; i++)
{
ct.ThrowIfCancellationRequested();
// 模拟耗时PLC读取(实际替换为 plc.ReadAsync)
await Task.Delay(150, ct);
float value = 20 + (float)Math.Sin(i * 0.1) * 30;
// 报告进度和值
progress.Report((i + 1, value));
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
_cts?.Cancel();
}
3. async/await + Dispatcher.Invoke(精细控制UI更新)
private async void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
try
{
for (int i = 0; i < 100; i++)
{
// 后台耗时操作
float value = await Task.Run(() =>
{
Thread.Sleep(150); // 模拟PLC读取
return 20 + (float)Math.Sin(i * 0.1) * 30;
});
// 手动回UI线程更新
Dispatcher.Invoke(() =>
{
progressBar.Value = i + 1;
lblStatus.Text = $"值:{value:F2}";
chart.Series[0].Points.AddXY(i, value);
});
}
MessageBox.Show("完成");
}
finally
{
Dispatcher.Invoke(() => btnStart.Enabled = true);
}
}
4. Reactive Extensions (Rx) 方式(适合复杂数据流)
// 需要 NuGet: System.Reactive
private IDisposable _subscription;
private void btnStart_Click(object sender, EventArgs e)
{
_subscription?.Dispose();
_subscription = Observable.Interval(TimeSpan.FromMilliseconds(200))
.ObserveOnDispatcher() // 自动切回 UI 线程
.Select(_ => ReadFromPLC()) // 后台读取
.Subscribe(
value =>
{
progressBar.Value += 1;
chart.Series[0].Points.AddXY(progressBar.Value, value);
},
ex => MessageBox.Show("错误:" + ex.Message),
() => MessageBox.Show("完成")
);
}
private float ReadFromPLC()
{
Thread.Sleep(150);
return 20 + (float)Math.Sin(DateTime.Now.TimeOfDay.TotalSeconds * 0.1) * 30;
}
总结对比(工业上位机视角)
| 维度 | BackgroundWorker | Task + Invoke | async/await + IProgress | Rx / Observable |
|---|---|---|---|---|
| 现代程度 | ★☆☆☆☆ | ★★★☆☆ | ★★★★★ | ★★★★☆ |
| 代码可读性 | 中 | 中低 | 高 | 中高(需熟悉Rx) |
| 取消支持 | 支持 | 手动实现 | 原生优秀 | 原生优秀 |
| 进度报告 | 原生 | 手动 | 原生优秀 | 原生优秀 |
| 异常处理 | 集中 | 分散 | 集中(try-catch await) | 集中 |
| 高频采集(>10Hz) | 不推荐 | 可行但繁琐 | 优秀 | 极优秀 |
| 学习成本 | 低 | 中 | 中低 | 高 |
| 工业推荐度(2025) | 不推荐 | 过渡期 | 强烈推荐 | 复杂数据流时推荐 |
最终推荐:
新项目 / 重构项目 → 优先使用 async/await + IProgress<T>
高并发、多数据源、复杂流 → 考虑 Rx 或 CommunityToolkit.Mvvm 的 Observable
老项目维护 → 逐步迁移到 async/await,保留 BackgroundWorker 兼容
后面更多精彩
- 完整异步批量PLC读取 + 缓冲 + UI批量刷新防卡顿
- WPF 版本(Dispatcher + IProgress)完整对比
- 高频采集(50Hz~100Hz)+ 数据缓冲 + 防卡死完整实现
- 集成到实际WinForms/WPF HMI(ZedGraph实时曲线 + 仪表盘)
- 异常隔离 + 日志 + 超时控制的综合案例
更多推荐

所有评论(0)