Reputation: 15847
In my application I startup a hidden dummyForm
that is merely created to keep track of the main UI thread. So If a new form is about to be created InvokeRequired
is used on the dummy form to make sure that we are on the main UI thread when creating the new form.
Directly after instantiating my frmStart
form I check the frmStart.InvokeRequired
and it is set to false so no need for invoke here (the same goes for dummyForm.InvokeRequired
).
Then I got a frmMyDialog
that will use frmStart
as parent/owner something like this:
using(Create frmMyDialog on main UI thread)
{
frmMyDialog.Show(frmStart);
}
This will throw a cross thread exception and the strange thing here is that:
frmMyDialog.InvokeRequired = false
dummyForm.InvokeRequired = false
frmStart.InvokeRequired = true
And this is even when I'm checking that dummyForm.InvokeRequired
is false when creating the frmStart
?
The frmMyDialog.InvokeRequired
should always be the same value as dummyForm.InvokeRequired
? What is happening here?
I have checked that the frmStart
and dummyForm
is not re-created at all after the first instance has been created.
Edit1:
This is how the application starts :
public static void Main(string[] args)
{
_instance = new MyClientMain(parameters);
Application.Run(_instance);
}
The constructor of MyClientMain class will run Setup on a static class called MainControl. MainControler will in the setup method instanciate a dummyform like this :
if (_dummyForm == null)
_dummyForm = new Form();
After this is done a login form will handle a login and this form is multithreaded. When the login is finished the MainController will be called again to instanciate and open the main MDI windo that holds frmStart. To make sure that we are on the same thread the following is done :
public static StartApplication()
{
if (_dummyForm.InvokeRequired)
_dummyForm.Invoke(new MethodInvoker(delegate { OpenMainOrbitWindow(); }));
//Instanciate mainform and frmStart then open mainForm with frmStart as a MDI child
}
There is no multithreading here.
Then when the service goes offline a event will be triggered and I need to popup a frmMyDialog but when using .ShowDialog() it dialog will be placed behind forms so the parent/owner most be found and set like this :
public static Form GetActiveForm()
{
Form activeForm = Form.ActiveForm;
if (activeForm != null)
return activeForm;
if (MainOrbitForm.TopMost)
return MainOrbitForm;
else
{
FormCollection openForms = Application.OpenForms;
for (int i = 0; i < openForms.Count && activeForm == null; ++i)
{
Form openForm = openForms[i];
if (openForm.IsMdiContainer)
return openForm.ActiveMdiChild;
}
}
if (_patientForm != null)
{
if (_patientForm.TopMost)
return _patientForm;
}
return null;
}
public static string ShowOrbitDialogReName()
{
frmMyDialog myDialog;
Form testForm;
//Makes sures that the frmOrbitDialog is created with the same thread as the dummyForm
//InvokeRequired is used for this
using (myDialog = MainController.CreateForm<frmOrbitDialog>())
{
//Settings...
testForm = GetActiveForm();
myDialog.ShowDialog(GetActiveForm(testForm));
}
}
The problem is that
myDialog.InvokeRequired = false
testForm.InvokeRequired = true;
MainController.DummyForm.InvokeRequired = false;
Edit2: Startup and creates the dummyform :
dummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9
After success login we create the mainform
_mainForm.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 9
Everything looks fine so far. Then a callback is received(WCF) and a event creates a frmMyDialog on the same thread(Invoke is used on the dummyForm) and then the ShowDialog is used :
frmMyCustomDialog.ShowDialog(_mainForm)
This throws a CrossThreadException and this is how it looks like at this point :
_mainForm.InvokeRequired = true
frmMyCustomDialog.InvokeRequired = false
MainControl.DummyForm.InvokeRequired = false
Thread.CurrentThread.ManagedThreadId = 12
Why is the MainControl.DummyForm not true? The ManageThreadId is not 9 but 12?
Upvotes: 4
Views: 664
Reputation: 4157
You should use System.Threading.SynchronizationContext.Current
. It was created directly for purposes like this.
You may access it anywhere, after the first form of your app have been created. Judging by your example below, this should not be a problem, since you create a form right o the start of application.
public static void Main(string[] args)
{
_instance = new MyClientMain(parameters);
Application.Run(_instance);
}
Then anywhere, you need the code to be executed on UI thread, you simply use
System.Threading.SynchronizationContext.Current.Send() // To execute your code synchronously
System.Threading.SynchronizationContext.Current.Post() // To execute your code synchronously
Mind, that SynchronizationContext is smartenough to see, that you call it already from UI thread, and then it will simply execute your delegate directly. And also, remember that you need to create some WinForms form or control before you use SynchronizationContext for the first time, becase when you do this, context will be initialized to appropriate implementation.
There are 3 implementation: Default, that does nothing - just always run code in sync, it stays in the Current until you create WinForms control or WPF control. Then Current will be populated with either context for Winforms, or context for WPF dispatcher.
Upvotes: 2
Reputation: 213
This is just off the top of my head, but as Vladimir Perevalov has stated in a different discussion, you have to make your form visible.
If your frmDummy is never shown, then it never has it's window handle created and assigned, and therefore it will always reply False to "InvokeRequired". This would mean that all the code you mean to sync through frmDummy is never actually sent to the initial UI thread, but is always run in the current thread. (which becomes an UI thread of its own for the control it has just created).
The important thing to note is that InvokeRequired tries to determine if the said control's window handle is owned by another thread. It has nothing to do with the constructor.
If you do not want to show the frmDummy, you can call CreateControl right after you instantiate it, to make sure it has it's handle assigned.
Upvotes: 1
Reputation: 8116
I didn't fully understand your question, because your examples don't really show anything about multithreading - however, if you want to create a form where the parent is another form from another thread, you could use this code:
public void CreateShowDialogForm()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(CreateShowDialogForm));
}
else
{
Form frmMyDialog = new Form();
frmMyDialog.Show(this);
}
}
private void Form4_Load(object sender, EventArgs e)
{
Task t = new Task(() => CreateShowDialogForm());
t.Start();
t.ContinueWith(task => true);
}
Upvotes: 0