C# 来自BackgroundWorker2\u Runworker的无效跨线程操作已在C中完成#

C# 来自BackgroundWorker2\u Runworker的无效跨线程操作已在C中完成#,c#,multithreading,backgroundworker,concurrent-processing,C#,Multithreading,Backgroundworker,Concurrent Processing,我犯了一个毫无意义的错误 跨线程操作无效:从创建控件的线程以外的线程访问控件“buttonOpenFile”。 在我的应用程序中,UI线程触发backgroundWorker1,在几乎完成时触发backgroundWorker2,并等待它完成backgroundWorker1等待backgroundWorker2完成,然后再完成自动重置事件变量用于在每个工作人员完成时进行标记。在backgroundWorker2\u RunWorkerComplete中,调用一个函数来重置表单控件。在该函数中引

我犯了一个毫无意义的错误

跨线程操作无效:从创建控件的线程以外的线程访问控件“buttonOpenFile”。

在我的应用程序中,UI线程触发
backgroundWorker1
,在几乎完成时触发
backgroundWorker2
,并等待它完成
backgroundWorker1
等待
backgroundWorker2
完成,然后再完成<代码>自动重置事件变量用于在每个工作人员完成时进行标记。在
backgroundWorker2\u RunWorkerComplete
中,调用一个函数来重置表单控件。在该函数中引发异常。我认为在
RunWorkerCompleted
函数中修改表单控件是安全的。这两个后台工作线程都是从UI线程实例化的。以下是我所做工作的一个非常概括的版本:

  AutoResetEvent evtProgrammingComplete_c = new AutoResetEvent(false);
  AutoResetEvent evtResetComplete_c = new AutoResetEvent(false);

  private void ResetFormControls()
  {
     toolStripProgressBar1.Enabled = false;
     toolStripProgressBar1.RightToLeftLayout = false;
     toolStripProgressBar1.Value = 0;

     buttonInit.Enabled = true;
     buttonOpenFile.Enabled = true; // Error occurs here.
     buttonProgram.Enabled = true;
     buttonAbort.Enabled = false;
     buttonReset.Enabled = true;
     checkBoxPeripheryModule.Enabled = true;
     checkBoxVerbose.Enabled = true;
     comboBoxComPort.Enabled = true;
     groupBoxToolSettings.Enabled = true;
     groupBoxNodeSettings.Enabled = true;
  }

  private void buttonProgram_Click(object sender, EventArgs e)
  {
     while (backgroundWorkerProgram.IsBusy)
        backgroundWorkerProgram.CancelAsync();

     backgroundWorkerProgram.RunWorkerAsync();
  }

  private void backgroundWorkerProgram_DoWork(object sender, DoWorkEventArgs e)
  {
     // Does a bunch of stuff...

     if (tProgramStat_c == eProgramStat_t.DONE)
     {
        tProgramStat_c = eProgramStat_t.RESETTING;

        while (backgroundWorkerReset.IsBusy)
           backgroundWorkerReset.CancelAsync();

        backgroundWorkerReset.RunWorkerAsync();
        evtResetComplete_c.WaitOne(LONG_ACK_WAIT * 2);

        if (tResetStat_c == eResetStat_t.COMPLETED)
           tProgramStat_c = eProgramStat_t.DONE;
     }
  }

  private void backgroundWorkerProgram_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  {
     // Updates form to report complete.  No problems here.

     evtProgrammingComplete_c.Set();
     backgroundWorkerProgram.Dispose();
  }

  private void backgroundWorkerReset_DoWork(object sender, DoWorkEventArgs e)
  {
     // Does a bunch of stuff...

     if (tResetStat_c == eResetStat_t.COMPLETED)
        if (tProgramStat_c == eProgramStat_t.RESETTING)
           evtProgrammingComplete_c.WaitOne();
  }

  private void backgroundWorkerReset_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  {
     CloseAllComms();
     ResetFormControls();
     evtResetComplete_c.Set();
     backgroundWorkerReset.Dispose();
  }

如果您有任何想法或建议,我们将不胜感激。我正在使用Microsoft Visual C#2008快速版。谢谢。

不能嵌套BackgroundWorker对象。如果可能的话,我建议使用.NET4.0任务,因为它们不会嵌套


只有使用库中的ActionDispatcher之类的东西才能嵌套BGWs。

在另一个线程中访问控件永远不会是线程安全的

您的问题是:“如何使用另一个线程访问UI线程创建的控件?”

答案是通过调用您的控件。这里有一个示例,您可以看到一个允许它的代码示例


这是否满足您的需要?

RunWorkerCompleted将在启动BackgroundWorker的线程上执行。由于您正在链接BackgroundWorkers(从1开始为2),2的RunWorkerCompleted将在1的线程上执行,而不是在UI线程上执行

您需要使用Invoke返回到UI线程,或者将UI更新移动到1的RunWorkerCompleted


我的建议是在更新UI时始终检查所需的Invoker,这样您就不必担心它来自哪个线程。

从技术上讲,2的RunWorkerCompleted将在完全不同的线程池线程上运行,而不是在1的线程上。谢谢,Ragoczy。因此,如果我理解正确,
RunWorkerCompleted
在调用
RunWorkerAsync
的线程上运行,而不一定是实例化(创建/声明)BackgroundWorker的线程。这是正确的吗?RunWorkerCompleted似乎是在随机线程池线程上运行的(我的快速测试只是碰巧重用了同一个线程)除非从主线程和实现ISynchronizeInvoke的类调用RunWorkerAsync——有趣的是,在我刚才有限的测试用例中,这两个类似乎都是必需的,而且可能还有更多。无论如何,如果您的ResetFormControls方法更改为check InvokeRequired,并在必要时调用,您应该可以这样做,因为这将允许您在将来实现其他线程选项(.net 4),同时保持窗体的接口线程安全。实际上,BGWs的要求是它们接受其“目标”SynchronizationContext.Current中的线程上下文。在WinForms应用程序中,当首次在该线程上创建Win32句柄时,会将其设置为WindowsFormsSynchronizationContext。我的回答提到了ActionDispatcher,它是我编写的一个类,提供了SynchronizationContext.Current,因此可以从ActionDispatcher的Run方法中“拥有”BGWs。谢谢,Stephen,但我的应用程序在嵌套的后台工作程序中表现良好(就“后台”任务而言)。我遇到的问题是在执行
RunWorkerCompleted
时更新表单控件。+1-在此之前没有听说过新的任务名称空间-它看起来非常有用。任务棒极了。并发集合和所有内容。NET4将使人们更容易使用魔鬼的作品。。。呃穿线。