2014-09-01 98 views
2

在下面的代码,当BackgroundWorker推出时,一SynchronizationContext确实存在,不过,该RunWorkerCompleted处理程序在不同的线程比RunWorkerAsync()执行,因此抛出异常。为什么?为什么'RunWorkerCompleted'在错误的线程上执行?

而当拨打tempForm被删除它运行良好。 (以及相同的取代一个Form一个MessageBox当存在对。)

(代码显示一个Form,启动一个BackgroundWorker引用另一个表格F1后一秒钟,然后示出了该第二表格F1

public static Form1 f1; 
static BackgroundWorker worker = new BackgroundWorker(); 


[STAThread] 
static void Main() 
{ 
    worker.DoWork += worker_DoWork; 
    worker.RunWorkerCompleted += worker_RunWorkerCompleted; 
    f1 = new Form1(); 
    using (Form1 tempForm = new Form1()) tempForm.ShowDialog(); 
    //MessageBox.Show("A MessageBox won't cause the exception later. Only the Form does."); 
    if (SynchronizationContext.Current == null) throw new Exception("This is NOT thrown"); 
    worker.RunWorkerAsync(); 
    Application.Run(f1); 
} 

static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    MessageBox.Show(f1, "Inside RunWorkerCompleted"); 
    //Throws: Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on. 
} 

static void worker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    Thread.Sleep(1000); 
} 

谁能请解释一下是怎么回事?

回答

5

问题是因为您从默认的同步上下文中调用RunWorkerAsync。作为一个小例子:

public static void Main() 
{ 
    var ctx1 = SynchronizationContext.Current; // returns null 
    var form = new Form(); 
    var ctx2 = SynchronizationContext.Current; // returns a WindowsFormsSyncContext 
    form.ShowDialog(); 
    var ctx3 = SynchronizationContext.Current; // returns a SynchronizationContext 

    worker.RunWorkerAsync(); // wrong context now 
} 

似乎实例化形式关联与当前线程一个WindowsFormsSynchronizationContext。有趣的是,在关闭表单后,关联的同步上下文将被设置为默认的同步上下文,即使用线程池的同步上下文。


一些挖后,我发现了原因 - 乍一看 - 奇怪的行为:的Control构造函数初始化WindowsFormsSynchronizationContext如果必要的话(见reference source)。一旦你从ShowDialog返回,那么将不会有任何消息循环,所以SynchronizationContext.Current必须重置,在这种情况下,默认线程池SynchronizationContext

+0

+1。谢谢。这看起来不错。 – ispiro 2014-09-01 14:38:15

0

Windows用户界面不是线程安全的,根本不支持多线程。出于这个原因,有一个检查哪个线程创建,然后尝试操作分配的图形资源。为避免该异常,你必须使用这里显示的调用模式:

if(InvokeRequired) 
{ 
    Invoke(worker_RunWorkerCompleted, sender, e); 
} 
else 
{ 
    MessageBox.Show(f1, "Inside RunWorkerCompleted"); 
} 

一个不同的线程中运行的方法是正常的事实。 Windows窗体由必须重入的入口线程构造,这意味着您不应该先阻塞(无限循环)运行程序的线程。

如果你仔细看看,在Main()是一个Run()方法的地方。这样做是为了使创建线程可以在桌面上继续自己的生活时自由终止。

+0

BackgroundWorker事件被设计为在调用RunWorkerAsync的线程上运行,因此如果您从UI线程调用该线程,则不需要InvokeRequired/Invoke。 – Dirk 2014-09-01 13:13:43

相关问题