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

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

介绍

当开发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 = (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
}

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

操作结束的截图如下:

时间: 2024-08-25 18:19:18

【翻译】C#中使用BackgroundWorker实现多线程的相关文章

C#中的BackgroundWorker控件

C#中的BackgroundWorker控件 Keywords: C# .NET BackgroundWorkerSource: http://txw1958.cnblogs.com/ BackgroundWorker是.NET Framework 里用来执行多线程任务的控件,它允许开发人员在一个单独的线程上执行一些操作.耗时的操作(如下载和数据库事务)在长时间运行时可能会导致用户界面 (UI) 始终处于停止响应状态.如果您需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用

BackgroundWorker 实现多线程操作

背景介绍: 在做程序的过程中,我们很可能遇到这样的情况:当我们执行一个比较耗时的操作,即界面加载数据量略大的时,在该操作未完成之前再去操作界面,就会出现停止响应的情况,这称为界面假死状态,那一个小圆圈转呀转的,想必大家看着就头疼.当然这是一个非常影响用户体验度的地方. 怎么做出一个能够及时响应的用户界面呢?多线程操作. 引入BackgroundWorker组件: BackgroundWorker是·net里用来执行多线程任务的控件,它允许编程者在一个单独的线程上执行一些操作. 常用方法 1.Ru

ios中的几种多线程实现

iOS 支持多个层次的多线程编程,层次越高的抽象程度越高,使用起来也越方便,也是苹果最推荐使用的方法.下面根据抽象层次从低到高依次列出iOS所支持的多线程编程范式:1, Thread;2, Cocoa operations;3, Grand Central Dispatch (GCD) (iOS4 才开始支持)下面简要说明这三种不同范式:Thread 是这三种范式里面相对轻量级的,但也是使用起来最负责的,你需要自己管理thread的生命周期,线程之间的同步.线程共享同一应用程序的部分内存空间,它

C#中利用委托实现多线程跨线程操作

在使用VS2005的时候,如果你从非创建这个控件的线程中访问这个控件或者操作这个控件的话就会抛出这个异常.这是微软为了保证线程安全以及提高代码的效率所做的改进,但是也给大家带来很多不便. 其实解决这个问题有两种方法:一,是通过设置System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;在你的程序初始化的时候设置了这个属性,而且在你的控件中使用的都是微软Framework类库中的控件的话,系统就不会再抛出你上面所说的

Linux中查看进程的多线程pstree, ps -L

Linux中查看进程的多线程 在SMP系统中,我们的应用程序经常使用多线程的技术,那么在Linux中如何查看某个进程的多个线程呢? 本文介绍3种命令来查看Linux系统中的线程(LWP)的情况:在我的系统中,用qemu-system-x86_64命令启动了一个SMP的Guest,所以有几个qemu的线程,以此为例来说明. 1. pstree 命令,查看进程和线程的树形结构关系.  BASH 1 2 3 4 5 [root@jay-linux ~]# pstree | grep qemu |-gn

boost中asio网络库多线程并发处理实现,以及asio在多线程模型中线程的调度情况和线程安全。

1.实现多线程方法: 其实就是多个线程同时调用io_service::run for (int i = 0; i != m_nThreads; ++i)        {            boost::shared_ptr<boost::thread> pTh(new boost::thread(                boost::bind(&boost::asio::io_service::run,&m_ioService)));            m_l

PHP中实现异步调用多线程程序代码

本文章详细的介绍了关于PHP中实现异步调用多线程方法,下面我们以给1000个用户发送一封推荐邮件,用户输入或者导入邮件账号了提交服务器执行发送来讲述. 比如现在有一个场景,给1000个用户发送一封推荐邮件,用户输入或者导入邮件账号了提交服务器执行发送 第一种解决方法: 代码如下: <?php $count=count($emailarr); for($i=0;$i<$count;$i ) { sendmail(.....);//发送邮件 } ?>              这段代码用户体

Vlang官网文档(中文翻译)-vlang中文文档--v0.1.0_20180625

PS:主要为自己学习,,,,看的时候顺便翻译的,,,渣翻(非全人工)勿喷. 介绍 V是一种用于构建可维护软件的静态类型编译编程语言. 它与Go相似,也受到Oberon.Rust.Swift的影响. V是一种非常简单的语言,阅读这份文档大概只需要半小时的时间,读完之后,您将学习到V的全部内容. 尽管很简单,但是它为开发人员提供了很多功能,你能用其它编程语言做的任何事情,都可以用V做到. Hello World fn main() { println('hello world') } 函数用 fn

◆◆0如何翻译smartform中的Text module

可以通过tcode SE63翻译smartform中的text module. SE63:点击other texts->B5->SSF 输入 object name也就是text module的名字,然后选择source language 和target language,最后点击Edit按钮. 翻译如下,别忘了激活. 最后用tcode slxt传输上面的翻译. 以上. 原文地址:https://www.cnblogs.com/lvdong18847870057/p/12555323.html