2012-02-22 109 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