以下是对 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 工业上位机推荐优先级)

  1. 首选async/await + IProgress<T>(最平衡、最现代、最易维护)
  2. 次选async/await + Dispatcher.Invoke(需要精细控制时)
  3. 复杂场景:Rx 或 CommunityToolkit.Mvvm 的 Observable(多路传感器、实时流)
  4. 坚决避免: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实时曲线 + 仪表盘)
  • 异常隔离 + 日志 + 超时控制的综合案例
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐