首页 > 代码库 > 【翻译】C#中使用BackgroundWorker实现多线程

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

介绍

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

 

背景

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

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

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

 

使用代码

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

 

创建应用程序

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

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

 

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

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

在窗体的代码里,我们声明一个类型为 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 = http://www.mamicode.com/(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 = http://www.mamicode.com/(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}

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

操作结束的截图如下:

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