C# BackgroundWorker 执行耗时任务,并更新UI

介绍

详见:
https://www.cnblogs.com/sparkdev/p/5906272.html

缺点:只能在 Form 中使用,复杂情况用 Task

添加 BackgroundWorker

代码

private BackgroundWorker _demoBGWorker = new BackgroundWorker();

设计器方式

更新UI

如果要更新UI,把 WorkerReportsProgress 属性设置为 true,否则报错

代码设置

_demoBGWorker.WorkerReportsProgress = true;

设计器设置

添加 DoWork 事件

在 DoWork 事件处理函数中执行 耗时任务

注意:不能直接访问控件、直接获取控件值,会报错 (需要额外代码)

代码添加

_demoBGWorker.DoWork += BGWorker_DoWork;

设计器添加

事件代码

private void BGWorker_DoWork(object sender, DoWorkEventArgs e)
{
    //在这里执行耗时的运算。
    int sum = 0;
    for (int i = 0; i <= 100; i++)
    {
        sum += i;
        string message = "Current sum is: " + sum.ToString();
        //ReportProgress() 方法把信息传递给 ProcessChanged 事件处理函数。
        //第一个参数类型为 int,表示执行进度。
        //如果有更多的信息需要传递,可以使用 ReportProgress 的第二个参数
        bgWorker.ReportProgress(i, message);
        Thread.Sleep(600);
    }
}

添加 ProgressChanged 事件

然后指定 ProgressChanged 事件处理方法,用于更新UI

代码添加

_demoBGWorker.ProgressChanged += BGWorker_ProgressChanged;

设计器添加

事件代码

private void BGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // 取出 ReportProgress() 方法的第一个参数,修改进度条
    this.progressBarSum.Value = e.ProgressPercentage;

    // 使用 e.UserState ,获取 ReportProgress() 方法的第二个参数
    string message = e.UserState.ToString();
}

调用 RunWorkerAsync() 方法启动

_demoBGWorker.RunWorkerAsync();

调用该方法后,开始执行 BGWorker_DoWork()

高级用法1-传参给 DoWork() 事件函数

调用 RunWorkerAsync() 方法传参给 DoWork() 事件处理函数

调用 RunWorkerAsync() 方法时传 100

_demoBGWorker.RunWorkerAsync(100);
private void BGWorker_DoWork(object sender, DoWorkEventArgs e)
{

    int endNumber = 0;
    if(e.Argument != null)
    {
        // 取出 100
        endNumber = (int)e.Argument;
    }

}

高级用法2-传参给 ProgressChanged() 事件函数

private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
{
    progressForm.initProgressBar(10);
    // 设置进度,并且传参数
    backgroundWorker2.ReportProgress(1,"哈哈");
}
private void backgroundWorker2_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // 取出参数
    string result = (string)e.UserState;
    if (result != null)
    {

    }
}

高级用法3-传参给 RunWorkerCompleted() 事件函数

BackgroundWorker 的 RunWorkerCompleted 事件可以通过 RunWorkerCompletedEventArgs 参数接收后台操作返回的值。以下是实现方式:

  1. 在 DoWork 事件处理程序中,将结果赋值给 DoWorkEventArgs.Result 属性

  2. 在 RunWorkerCompleted 事件处理程序中,通过 e.Result 获取该值

示例代码如下:

提示: BackgroundWorker1_RunWorkerCompleted() 方法处理是有顺序的

using System;
using System.ComponentModel;
using System.Windows.Forms;

public class ExampleForm : Form
{
    private BackgroundWorker backgroundWorker1;

    public ExampleForm()
    {
        // 初始化 BackgroundWorker
        backgroundWorker1 = new BackgroundWorker();
        backgroundWorker1.DoWork += BackgroundWorker1_DoWork;
        backgroundWorker1.RunWorkerCompleted += BackgroundWorker1_RunWorkerCompleted;

        // 启动后台操作
        backgroundWorker1.RunWorkerAsync();
    }

    // 后台操作
    private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        // 执行耗时操作
        string result = LongRunningOperation();

        // 将结果存储在 e.Result 中
        e.Result = result;
    }

    // 后台操作完成后执行
    private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // 检查是否有错误,如果 “获取后台操作返回的值” 在上面,当程序报错时,程序会出现异常
        if (e.Error != null)
        {
            MessageBox.Show($"发生错误: {e.Error.Message}");
            return;
        }

        // 检查是否取消了操作
        if (e.Cancelled)
        {
            MessageBox.Show("操作已取消");
            return;
        }

        // 获取后台操作返回的值
        string result = e.Result as string;

        if (result != null)
        {
            MessageBox.Show($"操作完成,结果: {result}");
        }
    }

    // 模拟耗时操作
    private string LongRunningOperation()
    {
        // 模拟耗时工作
        System.Threading.Thread.Sleep(3000);
        return "这是后台操作的结果";
    }
}

上述代码中:

  • DoWork 事件处理程序中执行后台操作,并将结果存储在 e.Result 中
  • RunWorkerCompleted 事件处理程序中通过 e.Result 获取结果
  • 可以传递任何类型的对象,不仅仅是字符串
  • 记得处理可能的错误和取消情况

如果需要传递多个值,可以使用自定义类、匿名类型或 Tuple 来包装多个值。

高级用法4-取消

执行耗时任务时,实现取消当前任务

设置属性

必须设置下面属性为 true 才能使用 取消 功能:

bgWorker.WorkerSupportsCancellation = true;

修改BGWorker_DoWork方法

在 DoWork 方法的 for 循环中,定期检查 CancellationPending 属性,如果为 true 表示取消

Thread.Sleep(600) 后面添加代码,如下:

bgWorker.ReportProgress(i, message);
Thread.Sleep(600);

//在操作的过程中需要检查用户是否取消了当前的操作。
if (bgWorker.CancellationPending == true)
{
    e.Cancel = true;
    break;
}

取消按钮的事件代码

点击取消按钮时要调用的代码:

bgWorker.CancelAsync();

处理取消结果

在 RunWorkerCompleted 事件中通过 e.Cancelled 判断是否为取消状态:

if (e.Cancelled)
{
    // 处理取消逻辑
}

高级用法5-执行结束、异常处理

指定 RunWorkerCompleted 事件处理函数

_demoBGWorker.RunWorkerCompleted += BGWorker_RunWorkerCompleted;
private void BGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //如果用户取消了当前操作就关闭窗口。
    if (e.Cancelled)
    {
        this.Close();
    }

    //计算已经结束,需要禁用取消按钮。
    this.btnCancel.Enabled = false;

    //计算过程中的异常会被抓住,在这里可以进行处理。
    if (e.Error != null)
    {
        Type errorType = e.Error.GetType();
        switch (errorType.Name)
        {
            case "ArgumentNullException":
            case "MyException":
                //do something.
                break;
            default:
                //do something.
                break;
        }
    }

    //计算结果信息:e.Result
    //use it do something.
}

RunWorkerCompleted 事件处理函数会在DoWork 事件处理函数返回后被调用。通过它我们可以进行一些运算结束后的操作,比如禁用取消按钮,异常处理,结果显示等。

注意

如果想要拿到 e.Result,需要在 BGWorker_DoWork() 方法中设置 e.Result属性,如:

e.Result = sum;