多个背景线程

时间:2012-09-04 09:50:01

标签: c# winforms multithreading backgroundworker

我有一个使用BackGroundWorker执行一组任务的c#应用程序:

 private void buttonStartCheckOut_Click(object sender, EventArgs e)
        {
                BackgroundWorker checkOuter = new BackgroundWorker();
                checkOuter.DoWork += new DoWorkEventHandler(checkOuter_DoWork);
                checkOuter.RunWorkerAsync();
                checkOuter.RunWorkerCompleted += new RunWorkerCompletedEventHandler(checkOuter_RunWorkerCompleted);
            }

        void checkOuter_DoWork(object sender, DoWorkEventArgs e)
        {
            if (textBoxCICheckOut.Text != "")
                CheckOutCI();

            if (textBoxCACheckOut.Text != "")
                CheckOutCA();

            if (textBoxCAuthCheckOut.Text != "")
                CheckOutCAuth();

            if (textBoxCLCheckOut.Text != "")
                CheckOutCL();

            if (textBoxCCCheckOut.Text != "")
                CheckOutCC();
        }

正如你所看到的,我只有2个线程;一个用于GUI,一个用于次要任务 当我完成所有功能时,我很容易跟踪 现在我想通过为CheckOutCI(),CheckOutCA()和其他人创建一个单独的线程来使它更快。创建5个后台工作者看起来有点脏。

我想问:
如何跟踪所有功能何时完成执行。

如果任何一个函数返回异常,我想将其显示给用户并要求用户更正用户并再试一次。我希望我能够正确解释我的问题 请根据我对帖子的评论,按照wdavo编辑代码。

3 个答案:

答案 0 :(得分:4)

我会看一下使用Task库(假设您运行的是.NET 4.5或更高版本)。在大多数情况下,我发现使用它比后台工作者好得多。

(注意你仍然可以在.NET 4中使用Task库,但是Task.WhenAll仅在4.5中可用)

http://msdn.microsoft.com/en-us/library/dd235618

如果不重写整个程序,这里有一个如何使用它的例子:

将简单条件逻辑移动到按钮

private void button1_Click(object sender, EventArgs e)
{
  var tasks = new List<Task>();

  if (Text == "A")
  {
    tasks.Add(funcA());
  }

  if (Text == "B")
  {
    tasks.Add(funcB());
  }

  //And so on....

  Task.WhenAll(tasks.ToArray()).ContinueWith(t =>
  {
    if (t.Exception != null)
    {
      //One of the tasks threw an exception
      MessageBox.Show("There was an exception!");
    }
    else
    {
      //None of the tasks threw an exception
      MessageBox.Show("No Exceptions!");
    }
  });

}

我们将任务添加到集合中,以便我们可以通过Task.WhenAll知道它们何时完成。当集合中的所有任务完成后,将显示一个消息框。如果集合中的任何任务抛出异常,则将填充“t”的Exception属性。特定异常作为此异常的内部异常存在。

将线程代码移动到单个任务/功能。您将创建结帐功能看起来与此类似:

private Task funcA()
{
  return Task.Factory.StartNew(() =>
  {
    try
    {
      //Code running here will be executed on another thread
      //This is where you would put your time consuming work
      //
      //
    }
    catch(Exception ex)
    {
      //Handle any exception locally if needed
      //If you do handle it locally, make sure you throw it again so we can see it in Task.WhenAll
      throw ex;
    }

    //Do any required UI updates after the work
    //We aren't on the UI thread, so you will need to use BeginInvoke
    //'this' would be a reference to your form
    this.BeginInvoke(new Action(() =>
    {
      //...
    }));

  });
}

这是做什么的以下

  • 创建并运行一个任务,该任务对线程池中的线程执行一些操作
  • 如果有异常,我们会在本地处理它。我们重新抛出异常,这样我们就可以知道任务在执行'Task.WhenAll'时失败了
  • 完成工作后更新UI。您需要调用BeginInvoke来在UI线程上运行代码,以避免交叉线程问题。

答案 1 :(得分:1)

启动比CPU或内核更多的线程实际上可以使您的应用程序变慢。当存在比CPU更多的CPU绑定线程时,操作系统需要在线程之间更频繁地进行上下文切换 - 这非常昂贵,并且可能导致操作系统在线程之间花费更多时间上下文切换,而不是让他们有时间工作。 p>

您可以使用并行任务库的并行方面自动在CPU之间分配负载。例如:

Action[] actions = new Action[] {CheckOutCI, CheckOutCA, CheckOutCAuth, CheckOutCL, CheckOutCC};
Parallel.ForEach(actions, e=>e());

......这不是你想要的;但应该给你一个大致的想法。例如根据当前条件填充actions

答案 2 :(得分:0)

您需要在backgroundworker中使用ReportProgress方法

void checkOuter_DoWork(object sender, DoWorkEventArgs e)
    {
        if (textBoxCICheckOut.Text != "")
            CheckOutCI();
            checkOuter.ReportProgress(completionPercentage,"Error message");

可以在checkOuter_ProgressChanged事件中捕获ReportProgress中发送的数据

 checkOuter_ProgressChanged(object sender,ProgressChangedEventArgs e)
 {
     int percentage = e.ProgressPercentage;
     string message = e.UserState;
 }