2009-06-03 88 views
2

.Net的奇怪的鎖定語義再次困擾着我。C#:等待變量變爲非空

我正在啓動一個線程,子線程依次啓動一個窗體。父線程應等到表單創建完畢。

我第一次嘗試使用監視器來監視表單變量:

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。我可以很容易地創建一個虛擬整數或其他東西(我真的認爲我會調整FormThread變量),但我想知道是否有更優雅的解決方案。

回答

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

不在當前線程上執行spin-wait刪除使用單獨線程lanch新表單的全部要點?除非我誤解了某些內容,否則你只是想同步創建新表單。 (有沒有它需要駐留在不同的STA的原因嗎?)

+0

看起來他想從其他經營他的主要應用循環由於某種原因,線程...? – jerryjvl 2009-06-03 13:36:36

+0

小問題是它讓我不用擔心主線程的線程模型,以及它是否支持表單。更大的好處是主線程獲得控制權返回給它,並且可以像單線程一樣執行操作,而此表單位於另一個線程中,並保持用戶進行娛樂和通知。我希望主線程的最終API非常好。 – Thanatos 2009-06-03 14:15:03

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存在後纔會退出。

+0

如果在`FromThread.Start`和`Monitor.Wait`之間調用Monitor.Pulse,會發生什麼? – Dialecticus 2013-01-18 12:18:59

+0

@Dialecticus請記住:唯一可以調用'Pulse'的人是擁有鎖定的人。直到你調用Wait,這個鎖正在使用中。所以對此的答案要麼是「不可能」,要麼是更明確:「調用Monitor.Pulse的線程將會得到一個異常,因爲它們沒有鎖*」 – 2013-01-18 12:23:27

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

使用靜態布爾來標記表單是否已加載。 它是原子,所以不需要鎖定。

在主代碼只是像做

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

真正的問題是爲什麼要這樣? 通常你想要主線程運行GUI的東西,輔助線程運行幫助代碼。 如果你解釋爲什麼你需要它,我們可以想出更好的技術。

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