首页 > 代码库 > 【翻译】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实现多线程