找回密码
 立即注册
快捷导航

[C#] C# WinForms中Invoke调用的优化思考

[复制链接]
admin 2024-12-14 07:28:42 | 显示全部楼层

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

问题描述

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

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;
        }
    }
}

C# WinForms中Invoke调用的优化思考5652 作者:admin 帖子ID:1169

这段代码存在以下问题:

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

优化方案

1. 缓存委托对象

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

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;
        }
    }
}

C# WinForms中Invoke调用的优化思考7126 作者:admin 帖子ID:1169

2. 使用 Progress<T>

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

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);
            }
        });
    }
}

C# WinForms中Invoke调用的优化思考258 作者:admin 帖子ID:1169

3. 批量更新策略

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

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

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. 综合示例:带取消和异常处理的进度更新

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

// 进度信息类  
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);
    }
}

C# WinForms中Invoke调用的优化思考8871 作者:admin 帖子ID:1169

总结

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

  1. 减少不必要的对象创建

  2. 提高应用程序的响应性

  3. 使代码更加清晰和易维护

  4. 避免潜在的内存问题

  5. 提供更好的用户体验

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

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

温馨提示

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

Archiver|手机版|小黑屋|DLSite

GMT+8, 2025-1-18 13:02

Powered by Discuz! X3.5 and PHP8

快速回复 返回顶部 返回列表