2012-02-22 86 views
0

我下面的代碼創建一個mdi窗體中的窗口。這個想法是創建一個特定類型的窗口,如果它存在的話,或者如果已經存在一個實例就把它放在前面。Wpf創建窗口鎖

public static object CreateWindow(Type windowType, params object[] args) 
    { 
     try 
     { 
      lock (_definitionToWindow) 
      { 
       var def = new WindowDefinition {ControlType = windowType, Args = args}; 

       System.Windows.Forms.Form win = null; 
       if (_definitionToWindow.TryGetValue(def, out win)) 
       {      
        win.Activate(); 
        return win; 
       }  

       System.Windows.Controls.Control uiElement = 
        (System.Windows.Controls.Control) Activator.CreateInstance(windowType, args); 


       object result = null; 
       if (uiElement is Window) 
        result = WpfMdiHelper.ShowWpfWindowInMdi((Window) uiElement); 
       else 
        result = WpfMdiHelper.ShowWpfControlInMdi((System.Windows.Controls.Control) uiElement); 

       if (result is System.Windows.Forms.Form) 
       {      
        _definitionToWindow.Add(def, result as System.Windows.Forms.Form); 
        lock (_windowslock) 
        { 
         _windows.Add((System.Windows.Forms.Form) result, uiElement as IHasViewModel);       
        } 
        ((System.Windows.Forms.Form) result).Disposed += new EventHandler(WindowsFactory_Disposed); 
       }     
       return result; 
      } 
     } 
     catch (Exception ex) 
     { 
      Logger.WriteError("Window creation exception", ex.ToString(), LogEntryCodes.UIException); 
     } 
     return null; 
    } 

代碼或多或少的作品,但是當你點擊它打開了多個窗口,快速連續打開了一扇窗幾種類型的按鈕。

運行調試跟蹤之後,我發現lock (_definitionToWindow)被所有的點擊繞過(看起來像是所有的調用都在同一個線程上)並且方法塊在Activator.CreateInstance上。所以當第二次調用到達字典時,檢查它沒有找到任何以前的實例並繼續重新創建窗口。

任何人都知道爲什麼會發生這種情況?以及處理這種情況的正確方法?

+0

爲了給出更多的信息,它看起來像是同一個線程(這解釋了爲什麼鎖不起作用)是由同一個線程在該代碼中執行兩次? – 2012-02-22 12:22:44

+1

在STA線程上使用* lock *是非法的。 CLR通過抽取消息循環來補償它。這將導致Windows消息觸發的任何事件(如點擊)的重入執行。只有在必要時纔在UI線程上運行與UI相關的代碼Dispatcher.BeginInvoke()。所以你永遠不需要使用* lock *。 – 2012-02-22 13:28:15

+0

感謝那些信息,我所做的大多數Windows編程都在ASP.NET中,所以我不知道這一點。在這種情況下,它實際上是在線程中運行的與UI相關的代碼,它創建並返回一個表單,但指向其他地方。 – 2012-02-22 15:29:31

回答

3

這應該給你一個線程安全鎖,即使它們在同一個線程上,也只允許一個調用者進入CreateWindowImpl。它不會像lock()那樣阻塞任何線程。

static long Locked = 0; 

static void CreateWindow(...) 
{ 
    if(0 == Interlocked.Exchange(ref Locked, 1)) 
    { 
     try 
     { 
     CreateWindowImpl(...); 
     } 
     finally 
     { 
     Interlocked.Exchange(ref Locked, 0); 
     } 
    } 
} 
+0

我敢肯定,它肯定會修復它,但是那裏的線程又如何呢? – 2012-02-22 12:23:06

+0

我想Activator.CreateInstance允許分派事件,以便重新輸入CreateWindow。所需要的只是一個簡單的布爾標誌(沒有線程安全性)。 – Phil 2012-02-22 12:26:57

+2

在這篇MSDN文章的最底部,http://msdn.microsoft.com/en-us/library/ms741870.aspx是一個註釋「WPF的任務是避免意外的重入而不重新引入內存泄漏,這就是爲什麼我們不會阻止任何地方的重入。「 – Phil 2012-02-22 12:32:23