Question

.Net's odd locking semantics are bugging me again.

I'm launching a thread, the child thread in turns starts a form. The parent thread should wait until the form is created.

My first attempt was to use a Monitor to watch the Form variable:

private void OpenForm()
{
    if (FormThread == null)
    {
        Monitor.Enter(Form);
        FormThread = new Thread(FormStub);
        FormThread.SetApartmentState(ApartmentState.STA);
        FormThread.Start();
        Monitor.Wait(Form);
        Monitor.Exit(Form);
    }
}

private void FormStub()
{
    Form = new ConnectorForm();
    Monitor.Enter(Form);
    Monitor.PulseAll(Form);
    Monitor.Exit(Form);
    Application.Run(Form);
}

... This throws an exception. Monitor.Enter() fails, since Form == null.

I could very easily create a dummy integer or something (I actually think I'll canabalize the FormThread variable), but I was wondering if there was a more elegant solution.

Was it helpful?

Solution

Better synchronisation primitive for this case:

private ManualResetEvent mre = new ManualResetEvent(false);

private void OpenForm()
{
    if (FormThread == null)
    {
        FormThread = new Thread(FormStub);
        FormThread.SetApartmentState(ApartmentState.STA);
        FormThread.Start();
        mre.WaitOne();
    }
}

private void FormStub()
{
    Form = new ConnectorForm();
    mre.Set();
    Application.Run(Form);
}

OTHER TIPS

Doesn't performing a spin-wait on the current thread delete the whole point of using a separate thread to lanch the new form? Unless I'm misunderstanding something, you just want to create the new form synchronously. (Is there any reason it needs to reside in a different STA?)

You could try the following, which uses a single object/Monitor as the message mechanism:

private void OpenForm()
{
    if (FormThread == null)
    {
        object obj = new object();
        lock (obj)
        {
            FormThread = new Thread(delegate () {
                lock (obj)
                {
                    Form = new ControllerForm();
                    Monitor.Pulse(obj);
                }
                Application.Run(Form);
            });
            FormThread.SetApartmentState(ApartmentState.STA);
            FormThread.Start();
            Monitor.Wait(obj);
        }
    }
}

The original thread holds the lock until it calls Monitor.Wait; this lets the second thread (already started) in to create the form, pulse the original thread back into life, and release; the original thread then exits only after Form exists.

I tend to use the AutoResetEvent for cases like these:

private AutoResetEvent _waitHandle = new AutoResetEvent(false);

private void OpenForm()
{
    Thread formThread = new Thread(FormStub);
    formThread.SetApartmentState(ApartmentState.STA);
    formThread.Start();
    _waitHandle.WaitOne();

    // when you come here FormStub has signaled                
}

private void FormStub()
{
    // do the work

    // signal that we are done
    _waitHandle.Set();
}

Another way to pass an EventWaitHandle is to pass it to FormStub as a parameter (so it does not clutter your object model):

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    EventWaitHandle e = new EventWaitHandle(false, EventResetMode.ManualReset);
    Thread t = new Thread(FormStub);
    t.SetApartmentState(ApartmentState.STA);
    t.Start(e);
    e.WaitOne();
}

static void FormStub(object param)
{
    EventWaitHandle e = (EventWaitHandle) param;
    Form f = new Form1();

    e.Set();
    Application.Run(new Form1());
}

Use a static bool to flag whether or not the Form has loaded. It is atomic so won't need locking.

In the main code just do something like

while(!formRun) { Thread.Sleep(100); }

The real question is why are you doing this? Usually you want the main thread to run GUI stuff, and secondary threads to run helper code. If you explain why you need it we can maybe come up with a better technique.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top