编辑
2025-02-11
C# 应用
00
请注意,本文编写于 84 天前,最后修改于 84 天前,其中某些信息可能已经过时。

目录

问题描述
优化方案
1. 缓存委托对象
2. 使用 Progress<T>
3. 批量更新策略
4. 使用 BeginInvoke 异步调用
5. 综合示例:带取消和异常处理的进度更新
总结

这也是一个网友提出这个问题,细想来还是可以优化一下,算是再熟悉明确一下这个吧。在 WinForms 开发中,跨线程更新 UI 是一个常见的场景。通常我们会使用 Control.InvokeControl.BeginInvoke 来确保 UI 更新在正确的线程上执行。但是,如果使用不当,这些调用可能会带来性能问题。让我们深入探讨这个话题。

问题描述

让我们先看一个典型的场景 - 进度条更新:

C#
public partial class Form1 : Form { private void btnStart_Click(object sender, EventArgs e) { Task.Run(() => { for (int i = 0; i <= 100; i++) { Thread.Sleep(50); // 模拟耗时操作 UpdateProgressBar(i); } }); } private void UpdateProgressBar(int value) { if (progressBar1.InvokeRequired) { progressBar1.Invoke(new Action<int>(UpdateProgressBar), value); } else { progressBar1.Value = value; } } }

image.png

这段代码存在以下问题:

  1. 每次调用都创建新的 Action<int> 委托对象
  2. 频繁的跨线程调用可能导致UI响应迟钝
  3. 同步调用 Invoke 会阻塞工作线程

优化方案

1. 缓存委托对象

第一个简单的优化是缓存委托对象:

C#
public partial class Form1 : Form { private readonly Action<int> _updateProgressBarAction; public Form1() { InitializeComponent(); _updateProgressBarAction = new Action<int>(UpdateProgressBar); } private void btnStart_Click(object sender, EventArgs e) { Task.Run(() => { for (int i = 0; i <= 100; i++) { Thread.Sleep(50); UpdateProgressBar(i); } }); } private void UpdateProgressBar(int value) { if (progressBar1.InvokeRequired) { progressBar1.Invoke(_updateProgressBarAction, value); } else { progressBar1.Value = value; } } }

image.png

2. 使用 Progress

更现代的方式是使用 Progress<T> 类:

C#
public partial class Form1 : Form { private readonly IProgress<int> _progress; public Form1() { InitializeComponent(); _progress = new Progress<int>(value => progressBar1.Value = value); } private async void btnStart_Click(object sender, EventArgs e) { await Task.Run(() => { for (int i = 0; i <= 100; i++) { Thread.Sleep(50); _progress.Report(i); } }); } }

image.png

3. 批量更新策略

如果更新频率过高,可以采用批量更新策略:

C#
public partial class Form1 : Form { private const int UpdateThreshold = 5; // 每5%更新一次 private async void btnStart_Click(object sender, EventArgs e) { var progress = new Progress<int>(value => progressBar1.Value = value); await Task.Run(() => { for (int i = 0; i <= 100; i++) { Thread.Sleep(50); if (i % UpdateThreshold == 0) { ((IProgress<int>)progress).Report(i); } } }); } }

4. 使用 BeginInvoke 异步调用

如果不需要等待UI更新完成,可以使用 BeginInvoke

C#
public partial class Form1 : Form { private readonly Action<int> _updateProgressBarAction; public Form1() { InitializeComponent(); _updateProgressBarAction = new Action<int>(UpdateProgressBarAsync); } private void btnStart_Click(object sender, EventArgs e) { Task.Run(() => { for (int i = 0; i <= 100; i++) { Thread.Sleep(50); UpdateProgressBarAsync(i); } }); } private void UpdateProgressBarAsync(int value) { if (progressBar1.InvokeRequired) { progressBar1.BeginInvoke(_updateProgressBarAction, value); } else { progressBar1.Value = value; } } }

5. 综合示例:带取消和异常处理的进度更新

下面是一个更完整的示例,包含了错误处理、取消操作和进度更新:

C#
// 进度信息类 public class ProgressInfo { public int Percentage { get; set; } public string Message { get; set; } } public partial class Form1 : Form { private CancellationTokenSource _cts; private readonly IProgress<ProgressInfo> _progress; private bool _isRunning; public Form1() { InitializeComponent(); // 初始化进度报告器 _progress = new Progress<ProgressInfo>(OnProgressChanged); InitializeControls(); } private void InitializeControls() { // 初始状态设置 btnCancel.Enabled = false; progressBar1.Minimum = 0; progressBar1.Maximum = 100; progressBar1.Value = 0; } private void OnProgressChanged(ProgressInfo info) { progressBar1.Value = info.Percentage; lblStatus.Text = info.Message; } private async void btnStart_Click(object sender, EventArgs e) { if (_isRunning) return; try { _isRunning = true; UpdateUIState(true); // 创建新的取消令牌源 _cts = new CancellationTokenSource(); // 执行长时间运行的任务 await ProcessLongRunningTaskAsync(_cts.Token); MessageBox.Show("处理完成!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (OperationCanceledException) { MessageBox.Show("操作已被用户取消", "已取消", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show($"处理过程中发生错误:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { _isRunning = false; UpdateUIState(false); _cts?.Dispose(); _cts = null; } } private void UpdateUIState(bool isProcessing) { btnStart.Enabled = !isProcessing; btnCancel.Enabled = isProcessing; } private async Task ProcessLongRunningTaskAsync(CancellationToken token) { // 模拟一个需要处理100个项目的长时间运行任务 const int totalItems = 100; await Task.Run(async () => { try { for (int i = 0; i <= totalItems; i++) { // 检查是否请求取消 token.ThrowIfCancellationRequested(); // 模拟处理工作 await Task.Delay(50, token); // 每处理一个项目报告进度 if (i % 5 == 0) { _progress.Report(new ProgressInfo { Percentage = i, Message = $"正在处理... {i}%" }); } } // 报告完成 _progress.Report(new ProgressInfo { Percentage = 100, Message = "处理完成" }); } catch (Exception) { // 确保在发生异常时更新UI显示 _progress.Report(new ProgressInfo { Percentage = 0, Message = "操作已取消" }); throw; // 重新抛出异常,让外层处理 } }, token); } private void btnCancel_Click(object sender, EventArgs e) { if (_cts?.IsCancellationRequested == false) { // 显示确认对话框 if (MessageBox.Show("确定要取消当前操作吗?", "确认取消", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { _cts?.Cancel(); lblStatus.Text = "正在取消..."; btnCancel.Enabled = false; } } } // 防止内存泄漏 protected override void OnFormClosing(FormClosingEventArgs e) { if (_isRunning) { e.Cancel = true; MessageBox.Show("请等待当前操作完成或取消后再关闭窗口", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } _cts?.Dispose(); base.OnFormClosing(e); } }

image.png

总结

在 WinForms 应用程序中,正确处理跨线程UI更新是很重要的。通过采用适当的模式和实践,我们可以:

  1. 减少不必要的对象创建
  2. 提高应用程序的响应性
  3. 使代码更加清晰和易维护
  4. 避免潜在的内存问题
  5. 提供更好的用户体验

选择哪种方式取决于具体的应用场景,但总的来说,使用 API(如 Progress<T> 和 async/await)通常是更好的选择。对于需要精细控制的场景,可以考虑使用缓存的委托对象和自定义的更新策略。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!