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?
source to share
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
.
source to share
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.
source to share