2

.Net 奇怪的锁定语义再次困扰着我。

我正在启动一个线程,子线程依次启动一个表单。父线程应该等到表单创建完成。

我的第一次尝试是使用 Monitor 来观察 Form 变量:

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

...这会引发异常。Monitor.Enter() 失败,因为 Form == null。

我可以很容易地创建一个虚拟整数或其他东西(我实际上认为我会canabalize FormThread 变量),但我想知道是否有更优雅的解决方案。

4

6 回答 6

4

这种情况下更好的同步原语:

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);
}
于 2009-06-03T13:35:50.403 回答
0

在当前线程上执行旋转等待不会删除使用单独线程启动新表单的全部意义吗?除非我误解了某些东西,否则您只想同步创建新表单。(是否有任何理由需要驻留在不同的 STA 中?)

于 2009-06-03T13:35:07.563 回答
0

您可以尝试以下方法,它使用单个object/Monitor作为消息机制:

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

原始线程持有锁直到它调用Monitor.Wait; 这让第二个线程(已经开始)创建表单,使原始线程恢复活力,然后释放;原始线程仅在存在后才退出Form

于 2009-06-03T13:35:24.677 回答
0

我倾向于将AutoResetEvent用于以下情况:

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();
}
于 2009-06-03T13:41:36.397 回答
0

传递 EventWaitHandle 的另一种方法是将其作为参数传递给 FormStub(因此它不会弄乱您的对象模型):

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());
}
于 2009-06-03T13:56:58.237 回答
-1

使用静态布尔值来标记表单是否已加载。它是原子的,因此不需要锁定。

在主代码中,只需执行类似的操作

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

真正的问题是你为什么要这样做?通常你希望主线程运行 GUI 的东西,而辅助线程运行帮助代码。如果您解释为什么需要它,我们可能会想出更好的技术。

于 2009-06-03T13:44:52.413 回答