Why is "RunWorkerCompleted" running on the wrong thread?

In the following code, when run BackgroundWorker

, there is SynchronizationContext

, but still the RunWorkerCompleted handler is executed on a different thread than RunWorkerAsync()

, and therefore throws an exception. Why?

And when the call tempForm

is removed, it works fine. (And the same for substituting a MessageBox

for a Form

there.)

(code shows Form

, launches BackgroundWorker

that links to another Form f1 one second later and then shows that second Form f1 .) sub>

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);
}

      

Can someone please explain what's going on here?

+3


source to share


2 answers


The problem is what you are calling RunWorkerAsync

from the default sync context. As a small example:

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
}

      

It looks like the form instance is binding a WindowsFormsSynchronizationContext

to the current thread. Interestingly, after the form is closed, the appropriate sync context will be set by default, that is, the one using the threadpool.




After some digging, I found the reason for the - at first glance - strange behavior: the constructor Control

initializes as needed WindowsFormsSynchronizationContext

(see the reference source ). After you return from ShowDialog

, then there will be no message loop, so there SynchronizationContext.Current

should be a reset, in this case for the default stream SynchronizationContext

.

+6


source


The Windows user interface is not thread safe and does not support multithreading. For this reason, there is a check on which thread is being created and then tries to manipulate the allocated graphics resources. To avoid an exception, MUST use the calling pattern shown here:

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

      



It is normal for another thread to run the method. Windows Forms are created by a principal thread that needs to be re-enabled, which means you shouldn't block (infinitely loop) the thread that starts the program at the beginning.

If you look closely, Main()

there is a method Run()

somewhere. This is so that the creating thread can be terminated while the form continues to live on the desktop.

0


source







All Articles