为什么'RunWorkerCompleted'在错误的线程上执行?

isp*_*iro 3 .net c# multithreading backgroundworker winforms

在下面的代码中,当BackgroundWorker启动时,SynchronizationContext 确实存在,但仍然,RunWorkerCompleted处理程序在不同的线程上执行RunWorkerAsync(),因此抛出异常.为什么?

当呼叫tempForm被删除时,它运行正常.(和同代以当MessageBoxForm那里.)

(代码显示a Form,启动一秒后BackgroundWorker引用另一个Form f1,然后显示第二个Form 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);
}
Run Code Online (Sandbox Code Playgroud)

谁能请解释这里发生了什么?

Dir*_*irk 6

问题是因为您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
}
Run Code Online (Sandbox Code Playgroud)

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


经过一番挖掘后,我发现了 - 乍一看 - 奇怪行为的原因:构造函数Control初始化WindowsFormsSynchronizationContext必要时(参见参考源).一旦从ShowDialog那里返回,就不会有任何消息循环,因此SynchronizationContext.Current必须重置,在这种情况下是默认的线程池SynchronizationContext.