首页 > 编程语言 > 详细

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

时间:2014-11-13 23:59:27      阅读:1619      评论:0      收藏:0      [点我收藏+]

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

介绍

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

 

背景

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

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

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

 

使用代码

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

 

创建应用程序

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

bubuko.com,布布扣

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

bubuko.com,布布扣

 

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

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

bubuko.com,布布扣

在窗体的代码里,我们声明一个类型为 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
}

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

bubuko.com,布布扣

操作结束的截图如下:

bubuko.com,布布扣

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

原文:http://www.cnblogs.com/equations/p/4096095.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!