2014-08-29 148 views
0

我有一个winforms应用程序,我在主窗体的Application.Run之前运行BackgroundWorker'RunWorkerCompleted'在错误线程上执行

BackgroundWorker的完成后,在其RunWorkerCompleted处理程序 - 它访问的主要形式,而我得到的异常:

“跨线程操作无效:控制‘Form1的’从访问 线程不是它创建的线程。“

因此,我认为错误曾与this comment其中指出RunWorkerCompleted

做“只会让UI线程上提出,如果UI线程创建的 BGW实例。”

(虽然它不是似乎像它是在一个单独的线程上创建的)。 (并且也参见this comment there)。

所以我创建了一个简单的测试,我BW.RunWorkerAsync();之前Application.Run(在“计划”),并在那里工作的罚款。抛出异常。

那么可能是什么问题?为什么与主窗体进行交互会抛出异常,但我正在从同一个线程运行BackgroundWorker

(我不能在这里张贴整个代码,因为它是很长,发帖只是相关的代码就是我前面提到的 - 。它抛出异常)

编辑

所以也许更具体的问题可以到位:如何让“UI线程创建BGW”?它是否必须在Application.Run中?显示表单后?是否不是取决于哪个线程创建了的BGW,而是哪个线程调用RunWorkerAsync?

EDIT 2

检查Thread.CurrentThread.ManagedThreadId我看到它的 RunWorkerAsync之前(因为它是DoWork +=RunWorkerCompleted +=前),但的RunWorkerCompleted处理内。

当单步执行代码并在RunWorkerAsync()之后等待几秒钟 - 它们都具有相同的线程ID,并且运行良好(所以不仅偶然,正确的线程被选中)!

+0

你试过调用形式? (另外,你正在寻找一个解决方案或解释) – Sayse 2014-08-29 10:21:41

+0

我试图更好地理解它,所以我可以修复它不_need_“调用”(并避免在未来更多的陷阱)。 – ispiro 2014-08-29 10:22:23

+7

*我不能在这里发布整个代码,因为它很长。只发布相关的代码就是我之前提到的 - 它不会抛出异常*那么您希望我们如何回答?即使我们这样做,它将纯粹是一种**猜测**,没有一些代码可以再现问题。 – 2014-08-29 10:25:55

回答

0

下面是其工作的例子:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Drawing; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace WindowsFormsApplication2 
{ 
    static class Program 
    { 
     /// <summary> 
     /// The main entry point for the application. 
     /// </summary> 
     [STAThread] 
     static void Main() 
     { 
      Form1 form = null; 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 

      BackgroundWorker workerThread = new BackgroundWorker(); 
      workerThread.DoWork += delegate 
      { 
       Thread.Sleep(1500); 
      }; 
      workerThread.RunWorkerCompleted += delegate 
      { 
       if (form != null) 
        form.BackColor = Color.Red; 
      }; 
      workerThread.RunWorkerAsync(); 

      form = new Form1(); 
      Application.Run(form); 
     } 

    } 
} 

好,它有一些种族问题。但我没有看到任何问题。运行和主线程是相同的UI线程 - 您可以在调试中检查它。 如何制造异常?

+0

这不会在UI线程中运行完成的事件。 – Servy 2014-08-29 14:06:19

+0

可笑。尝试运行和调试它,你会发现它实际上是一个UI线程。也没有例外,为什么?因为它的UI线程! ) – norekhov 2014-08-30 18:45:11

+0

不,它不会,因为当你启动worker来捕获时没有当前的同步上下文,所以它会在后台线程中触发完成的事件。 – Servy 2014-09-02 13:48:56

2

强烈暗示您从错误的线程调用RunWorkerAsync()。 BGW需要确定哪个线程在其上运行其事件。它本身无法做到,它需要帮助。你可以简单地添加一些诊断代码到你的程序以确认此帮助提供:

public static class DebugUtils { 
    public static void CheckThreadState() { 
     if (System.Threading.SynchronizationContext.Current == null) { 
      throw new InvalidOperationException("You are on the wrong thread") 
     } 
    } 
} 

而且插入在你打电话的RunWorkerAsync(代码的所有地方)这一呼吁:

DebugUtils.CheckThreadState(); 
0

BGW不能在UI线程中神奇地运行代码。它需要有一些机制,通过它知道UI线程,以便它可以封送事件处理程序。

它实际使用的是SynchronizationContext.Current。当你调用RunWorkerAsync然后使用该上下文来封送事件处理程序时,它会“记住”当前的上下文。这是对Application.Run的调用,它为您的UI的消息循环创建同步上下文,所以既然您在启动工作者之前甚至没有消息循环或同步上下文,它也无法在那里封送代码。

您需要等待才能启动后台工作人员,直到您确实收到了消息循环。这样做的一个简单的方法是在窗体的事件,直到消息循环已经建立,如Load事件,将不会触发启动它:

form.Load += (s, args) => worker.RunWorkerAsync(); 
+0

这听起来合乎逻辑。但是,正如我所说 - 我尝试了一个简单的例子 - 在Application.Run之前运行'RunWorkerAsync();'并且运行良好。 – ispiro 2014-08-29 14:19:03

+0

@ispiro可能会发生任何数量的事情。在没有真正知道你做了什么的情况下,没有什么可说的。你可以抑制异常,而不需要实际封送到UI线程,当你认为自己是用户的时候,你不能修改UI对象,你可能已经以一种确实创建了上下文的方式来设置事物,等等 – Servy 2014-08-29 14:22:20