views:

1152

answers:

6

.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.

A: 

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?)

Noldorin
It looks like he wants to run his main application loop from the other thread for some reason...?
jerryjvl
Thanatos
A: 

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.

Marc Gravell
+4  A: 

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);
}
jerryjvl
This seems to be exactly the primitive I was looking for. I missed it when I read the list of stuff in System.Threading, apparently... my eyeballs were looking for "Event" or something.
Thanatos
A: 

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();
}
Fredrik Mörk
A: 

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.

Chris
Better hope that formRun is volatile, then.. that isn't guaranteed to ever exit otherwise.
Marc Gravell
A: 

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());
}
James Hugard