【翻译】C#中使用BackgroundWorker实现多线程

原文地址:MultiThreading Using a Background Worker, C#

介绍

当开发Windows Forms应用程序时,你会常常注意到:当执行某个耗时的操作,比如处理一个打文件或是从远程服务器请求数据 ,用户界面会进入假死状态。这是由于你的应用程序是运行在单线程下。这个线程负责响应用户界面的操作,同时也负责处理应用程序中所有的事件和方法。因此,耗时的操作会阻塞你的用户界面,直到操作完成。今天,我们将要做的是把这些耗时的操作移到另一个不同的线程中,当以在另一边执行操作是,这样可以保持用户界面顺畅。

 

背景

在这个例子中,我们将用到Microsoft BackgroundWorker类,关于这个类更多的信息可以在这里找到。

我们将创建一个简单的执行耗时操作的应用程序,并且像用户显示最终结果。耗时操作将在另一个线程中执行,并且在操作执行期间将不断以操作进度更新用户界面提示信息。我们将允许用户在任何时候取消操作。

请记住:只有主线程才能访问用户界面,换句话说,你不能在另外的线程中访问用户控件。下面我们将详细讲解。

 

使用代码

我将会一步步的展示应用程序中使用的代码,最后我会附上源代码。

 

创建应用程序

我们将在Microsoft VS中创建一个简单的Windows Forms应用程序,我用的是Visual Studio 2010。像下图一样创建一个新的Windows Forms应用程序。我更喜欢使用C#,你也可以使用VB.NET.

1.png

如下图一样设置布局。我个人喜欢用Table布局面板来组织我的控件。这可以使空间保持有序,当窗体放大或是调整大小的时候。我们要做的是添加一个TextBox(设置成Multiline模式)来展示工作线程的结果,一个NumbericUpAndDown允许我们炫证数字,一个开始按钮和一个结束按钮。

2.png

 

在工具箱中,菜单和工具栏下,选择添加一个StatusStrip。这使得我们可以添加一个状态标签,我们将在标签上向用户显示进度信息。3.png

在StatusStrip里,单击左下角的小箭头,选择添加一个StatusLabel。重命名标签为lblStatus, 并且包它的Text属性设成空。

4.png

在窗体的代码里,我们声明一个类型为 BackgroundWorker的对象:

private BackgroundWorker myWorker = new BackgroundWorker();

在窗体的构造函数中,以下面的属性初始化我们刚刚创建的worker:

  • DoWork 事件处理程序,会在background worker 开始异步工作时调用。就是在这个事件中我们来处理耗时操作,比如调用远程服务器,查询数据库,处理文件…… 这个事件是在新的线程上执行的,这意味着在这个方法中我们不能访问用户控件。
  • RunWokerCompleted事件,在background worker 完成操作、取消操作或是发生异常的时候调用。这个事件是在主线程上被调用的,意味着在这个方法中我们可以访问用户控件。
  • ProgressChanged事件处理程序,当background worker的ReportProgress调用的时候触发。我们使用这个方法来向用户界面书写进度信息。这个事件是在主线程上调用的,意味着在这个方法中我们可以访问用户控件。
  • WorkerReportsProgress属性,用来指示worker是否可以向主线程报告进度。
  • WorkerSupportsCancellation属性,用来指示worker是否可以根据用户的请求取消操作。

下面是构造函数的完成代码:

public Form1()
        {
            InitializeComponent();

            myWorker.DoWork += new DoWorkEventHandler(myWorker_DoWork);
            myWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(myWorker_RunWorkerCompleted);
            myWorker.ProgressChanged += new ProgressChangedEventHandler(myWorker_ProgressChanged);
            myWorker.WorkerReportsProgress = true;
            myWorker.WorkerSupportsCancellation = true;
        }

现在我们来声明worker的事件处理函数:

  • DoWork 事件处理函数有两个参数,一个sender,和一个DoWorkEventArgs参数
protected void myWorker_DoWork(object sender, DoWorkEventArgs e)
{

}
  • RunWorkerCompleted事件处理函数有两个参数,一个sender,和一个RunWorkerCompletedEventArgs参数
protected void myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{

}
  • ProgressChanged事件处理函数有两个参数,一个sender,和一个ProgressChangedEventArgs参数
protected void myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{

}

下面我们创建一个接收一个整数的帮助方法,用这个整数乘以1000,线程sleep250ms,然后返回结果。这是你的应用程序可能执行的耗时操作的一个模拟,你可以改变sleep的间隔 。注意,应为这个方法是在DoWork事件里被调用的,这是后台线程会sleep给定的时间,并不是主线程。函数如下:

private int PerformHeavyOperation(int i)
{
    System.Threading.Thread.Sleep(250);
    return i * 1000;
}

转到设计界面,双击Start按钮,转到它的事件处理函数中。我们要做的是从NumericUpAndDown控件中取得数值,把这个数值传给异步线程,然后开始执行background worker。我们需要在这里获取数字控件的值,因为一旦我们到了新线程里,我们就不能访问用户控件狼来了。为了开始background worker的执行,我们调用了RunWorkerAsync方法。这个方法接收一个object参数,这个参数将传给后台线程,在这个object参数里,我们可以放入任意多个控件的值。为了传入不止一个值,我们使用object的数组。下面是btnStart_Click的完成代码。注意正在执行的worker,不能被再次调用,你会获得一个运行时错误如果你这么做的话。

private void btnStart_Click(object sender, EventArgs e)
{
    int numericValue = (int)numericUpDownMax.Value;//Capture the user input
    object[] arrObjects = new object[] { numericValue };//Declare the array of objects
    if (!myWorker.IsBusy)//Check if the worker is already in progress
    {
        btnStart.Enabled = false;//Disable the Start button
        myWorker.RunWorkerAsync(arrObjects);//Call the background worker
    }
}

现在在DoWork事件处理函数里,我们可以处理所有的耗时操作。首先,我们接收从主线程中获取到的对象,然后处理它们,最后把结果返回给主线程。记住只有主线程才能访问用户控件。当我们处理值的时候,我们会一直做两件事:

  • 用ReportProgress方法向主线程报告进度
  • 检查background worker的CancellationPeding属性,检查用户是否有取消命令

最后,我们把结果放到DoWorkEventArgs参数的Result属性里,这将会被RunWorkerCompleted事件捕获。下面是DoWork的完整代码:

protected void myWorker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker sendingWorker = 
    (BackgroundWorker)sender;//Capture the BackgroundWorker that fired the event
    object[] arrObjects = 
    (object[])e.Argument;//Collect the array of objects the we received from the main thread

    int maxValue = (int)arrObjects[0];//Get the numeric value 
            //from inside the objects array, don't forget to cast
    StringBuilder sb = new StringBuilder();//Declare a new string builder to store the result.

    for (int i = 1; i <= maxValue; i++)//Start a for loop
    {
        if (!sendingWorker.CancellationPending)//At each iteration of the loop, 
                    //check if there is a cancellation request pending 
        {
            sb.Append(string.Format("Counting number: {0}{1}", 
            PerformHeavyOperation(i), Environment.NewLine));//Append the result to the string builder
            sendingWorker.ReportProgress(i);//Report our progress to the main thread
        }
        else
        {
            e.Cancel = true;//If a cancellation request is pending, assign this flag a value of true
            break;// If a cancellation request is pending, break to exit the loop
        }
    }

    e.Result = sb.ToString();// Send our result to the main thread!
}

接着我们处理ProgressChanged事件。我们获取到调用ReportProgress方法时传入的整数值。注意你可以传任何类型的对象,通过使用ProgressChangedEventArgs参数的UserState属性。在这你除了显示状态信息,还可使用进度条来显示当前进度。

protected void myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    //Show the progress to the user based on the input we got from the background worker
    lblStatus.Text = string.Format("Counting number: {0}...", e.ProgressPercentage);
}

接着是RunWorkerCompleted事件。我们首先检查worker是否被取消,或是有任何错误发生。然后,我们我们获取background worker计算的结果,用于显示给用户:

protected void myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled && 
    e.Error == null)//Check if the worker has been canceled or if an error occurred
    {
        string result = (string)e.Result;//Get the result from the background thread
        txtResult.Text = result;//Display the result to the user
        lblStatus.Text = "Done";
    }
    else if (e.Cancelled)
    {
        lblStatus.Text = "User Canceled";
    }
    else
    {
        lblStatus.Text = "An error has occurred";
    }
    btnStart.Enabled = true;//Re enable the start button
}

还剩最后一件事,我们需要实现取消按钮。双击cancel按钮,进入后台代码,调用 background worker的CancelAsync方法。这会把worker的CancellationPending标志设为true. 我们会在DoWork事件处理程序的循环中检查这个标志。至此,我们可以总结得到终止一个正在运行的backgroundworker并不会立刻生效,如果background worker正在处理某件事,我们需要等待它完成才能取消操作。下面是btnCancel_Click的代码:

private void btnCancel_Click(object sender, EventArgs e)
{
    myWorker.CancelAsync();//Issue a cancellation request to stop the background worker
}

最后,运行程序的截图如下:

5.png

操作结束的截图如下:

6.png

转载于:https://www.cnblogs.com/equations/p/4096095.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用BackgroundWorker开启多线程时,需要先创建一个BackgroundWorker对象,然后设置DoWork事件处理程序。接着在主线程调用BackgroundWorker的RunWorkerAsync方法,该方法将启动一个新线程并执行DoWork事件处理程序。可以通过设置BackgroundWorkerWorkerReportsProgress和WorkerSupportsCancellation属性,使其支持进度报告和取消操作。 以下是一个简单的示例代码,演示如何使用BackgroundWorker开启多线程: ```c# private BackgroundWorker worker; private void buttonStart_Click(object sender, EventArgs e) { // 创建BackgroundWorker对象 worker = new BackgroundWorker(); // 设置DoWork事件处理程序 worker.DoWork += new DoWorkEventHandler(worker_DoWork); // 设置支持进度报告和取消操作 worker.WorkerReportsProgress = true; worker.WorkerSupportsCancellation = true; // 启动新线程 worker.RunWorkerAsync(); } private void worker_DoWork(object sender, DoWorkEventArgs e) { // 执行耗时操作 for (int i = 1; i <= 100; i++) { // 发送进度报告 worker.ReportProgress(i); // 检查是否取消操作 if (worker.CancellationPending) { e.Cancel = true; return; } // 模拟耗时操作 Thread.Sleep(50); } } private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) { // 更新进度条 progressBar1.Value = e.ProgressPercentage; } private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // 检查是否取消操作 if (e.Cancelled) { MessageBox.Show("操作已取消"); } else if (e.Error != null) { MessageBox.Show("发生错误:" + e.Error.Message); } else { MessageBox.Show("操作已完成"); } } ``` 在上述代码,buttonStart_Click事件处理程序创建了一个BackgroundWorker对象,并设置DoWork事件处理程序、支持进度报告和取消操作。然后调用RunWorkerAsync方法启动新线程。在worker_DoWork事件处理程序执行耗时操作,并发送进度报告。在worker_ProgressChanged事件处理程序更新进度条。在worker_RunWorkerCompleted事件处理程序检查是否取消操作或发生错误,并显示相应的消息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值