ShowDialog方法挂起而没有显示窗口??死锁?

Ign*_*cia 5 .net wpf message-pump hang showdialog

我们有一个WPF忙窗口指示器。它在主线程上使用表示window.ShowDialog()。响应Loaded事件后,将执行操作并关闭窗口,以便应用程序继续其工作。

window.ShowDialog()似乎从时间而不显示对话框,Loaded事件不会触发这样的应用程序挂起挂时间(很少)。相关代码如下:

private void BusyIndicatorAsyncCall(string text, Action<DoWorkEventArgs> doWorkDinamicView = null, Action doWork = null, Action workCompleted = null, Action<Exception> exceptionReturn = null)
{
    Window window = this.CreateWindowOfBusyIndicator(text);
    Dispatcher dispatcher = window.Dispatcher;
    BackgroundWorker backgoundworker = new BackgroundWorker();
    IViewModel viewModel = (window.Content as UserControl).DataContext as IViewModel;

    this.Modals.Add(viewModel, window);
    if (doWorkDinamicView != null)
    {
        DoWorkEventArgs eventArgs = new DoWorkEventArgs(window.Content);
        backgoundworker.DoWork += (s, e) => doWorkDinamicView.Invoke(eventArgs);
    }
    else if (doWork != null)
    {
        backgoundworker.DoWork += (s, e) => { doWork.Invoke(); };
    }

    backgoundworker.RunWorkerCompleted += (s, e) =>
    {
        Exception exception = e.Error;
        if (exception == null)
        {
            if (workCompleted != null)
            {
                try
                {
                    this.StyleName = null;
                    workCompleted.Invoke();
                }
                catch (Exception ex)
                {
                    exception = ex;
                }
            }
        }
        this.Modals.Remove(viewModel);
        dispatcher.Invoke(new Action(window.Close));
        if (exception != null)
        {
            if (exceptionReturn == null)
                throw new Exception("Error en RunWorkerCompleted.", exception);
            else
                exceptionReturn(exception);
        }
    };

    RoutedEventHandler onLoaded = new RoutedEventHandler((sender, e) =>
                            {
                                try
                                {
                                    backgoundworker.RunWorkerAsync();
                                }
                                catch
                                {
                                }
                            });
    this.BusyIndicatorImpl(window, onLoaded);
}

private void BusyIndicatorImpl(Window window, RoutedEventHandler onLoaded)
{
    window.Loaded += onLoaded;
    window.ShowDialog();
    window.Loaded -= onLoaded;
}
Run Code Online (Sandbox Code Playgroud)

当应用程序挂起时,我可以在该window.ShowDialog()方法上看到指令指针,但是该窗口在应用程序上不可见并且backgroundWorker尚未启动,因此我的猜测是尚未引发OnLoaded事件。

该应用程序并未真正挂起,因为它可以正确重绘,但是您无法在屏幕上的任何位置单击。作为应用程序挂起时的一个怪异效果,它在Windows 7的任务栏中消失了。

当我中断执行时,看到的调用堆栈如下:

user32.dll!_NtUserGetMessage@16()  + 0x15 bytes 
user32.dll!_NtUserGetMessage@16()  + 0x15 bytes 
[Managed to Native Transition]  
WindowsBase.dll!MS.Win32.UnsafeNativeMethods.GetMessageW(ref System.Windows.Interop.MSG msg, System.Runtime.InteropServices.HandleRef hWnd, int uMsgFilterMin, int uMsgFilterMax) + 0x14 bytes  
WindowsBase.dll!System.Windows.Threading.Dispatcher.GetMessage(ref System.Windows.Interop.MSG msg, System.IntPtr hwnd, int minMessage, int maxMessage) + 0x80 bytes 
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) + 0x75 bytes        WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes    
PresentationFramework.dll!System.Windows.Window.ShowHelper(object booleanBox) + 0x17d bytes 
PresentationFramework.dll!System.Windows.Window.Show() + 0x5c bytes 
PresentationFramework.dll!System.Windows.Window.ShowDialog() + 0x27d bytes  
>   xxx.dll!xxx.BusyIndicatorImpl(System.Windows.Window window, System.Windows.RoutedEventHandler onLoaded) Line 743 + 0xd bytes    C#
Run Code Online (Sandbox Code Playgroud)

正如其他人所说的那样,它看起来像是死锁,所以我在Visual Studio中进行了转储,并在其中运行了一些工具来寻找死锁。这是应用程序正在运行的线程的信息:

Debugger Thread ID  Managed Thread ID   OS Thread ID    Thread Object   GC Mode     Domain  Lock Count  Apt Exception
0                   1                   5684            2b2390          Preemptive  9176600 0           STA  
6                   2                   5572            2c7a80          Preemptive  2a82f8  0           MTA (Finalizer) 
7                   3                   3676            2cb828          Preemptive  2a82f8  0           Unknown  
11                  4                   864             7f7d5c0         Preemptive  2a82f8  0           MTA (Threadpool Worker) 
15                  10                  4340            921cdc8         Preemptive  9176600 1           MTA  
16                  12                  1648            9438560         Preemptive  2a82f8  0           MTA (Threadpool Completion Port) 
17                  14                  3380            9001038         Preemptive  2a82f8  0           Unknown (Threadpool Worker) 
21                  7                   5336            9002fe8         Preemptive  2a82f8  0           MTA (Threadpool Worker) 
20                  5                   4120            9003fc0         Preemptive  2a82f8  0           MTA (Threadpool Worker) 
25                  18                  5172            9004508         Preemptive  2a82f8  0           MTA (Threadpool Worker) 
27                  11                  5772            9003a78         Preemptive  2a82f8  0           MTA (Threadpool Worker) 
Run Code Online (Sandbox Code Playgroud)

只有一个线程带有应用程序代码(0,使用ShowDialog调用管理1)。其他线程没有应用程序代码,线程15(托管10)是唯一带有某些.Net代码的线程。

查看线程15(托管10),因为它是一个带锁的线程,我看到以下调用堆栈:

[[HelperMethodFrame_1OBJ] (System.Threading.WaitHandle.WaitMultiple)] System.Threading.WaitHandle.WaitMultiple(System.Threading.WaitHandle[], Int32, Boolean, Boolean) 
mscorlib_ni!System.Threading.WaitHandle.WaitAny(System.Threading.WaitHandle[], Int32, Boolean)+92 
System_ni!System.Net.TimerThread.ThreadProc()+28f 
mscorlib_ni!System.Threading.ThreadHelper.ThreadStart_Context(System.Object)+6f 
mscorlib_ni!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+a7 
mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+16 
mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+41 
mscorlib_ni!System.Threading.ThreadHelper.ThreadStart()+44 
[[GCFrame]] 
[[DebuggerU2MCatchHandlerFrame]] 
[[ContextTransitionFrame]] 
[[DebuggerU2MCatchHandlerFrame]] 
Run Code Online (Sandbox Code Playgroud)

这个调用栈看起来像一个计时器,等待着被解雇给我,但也许我对这种解释是错误的,所以我不知道该如何继续。

我可以根据要求提供您需要的任何信息。我不是WinDbg的专家,但是我已经开始能够处理它,因此您也可以要求我获取有关它的信息。

更新:

将一些日志添加到应用程序后,我们具有以下额外信息:

问题在于该方法dispatcher.Invoke(new Action(window.Close));被调用并且在执行时不会抛出异常,但是该方法window.ShowDialog();不返回。

我们已经尝试使用Spy ++和类似的工具来查找窗口,据我所知,该窗口不存在,但window.ShowDialog();仍在执行。

我希望这可以使您对正在发生的事情有所了解。

Ign*_*cia 3

一年后我找到了问题的原因,以下代码显示了正在发生的事情的概念证明:

总结一下(由于实际代码中的竞争条件),有 3 个具有父子关系 (A-->B-->C) 的窗口,并且代码正在关闭 B。这适用于 WPF 应用程序(并且 C 得到也关闭了)但在 VSTO 加载项中不起作用并挂起,B 永远不会离开 ShowDialog 方法:

为 Office Word 2010 创建一个新的 VSTO 项目并粘贴以下代码(不确定如果您针对不同的 Office 版本会发生什么):

using System.Diagnostics;
using System.Windows;
using System.Windows.Interop;
using Action = System.Action;

namespace WordAddIn1HangTest
{
    public partial class ThisAddIn
    {
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
            Window window1 = new Window();
            window1.Content = "1";
            Window window2 = new Window();
            window2.Content = "2";
            WindowInteropHelper windowInteropHelper1 = new WindowInteropHelper(window1);
            WindowInteropHelper windowInteropHelper2 = new WindowInteropHelper(window2);

            System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
            {
                windowInteropHelper1.Owner = Process.GetCurrentProcess().MainWindowHandle;
                window1.ShowDialog();
                MessageBox.Show("Hello");
            }));

            System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
            {
                windowInteropHelper2.Owner = windowInteropHelper1.Handle;
                window2.ShowDialog();
            }));

            System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
            {
                window1.Close();
            }));
        }

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
        }

        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }

        #endregion
    }
}
Run Code Online (Sandbox Code Playgroud)

当 window1 关闭时,您应该会看到消息“Hello”。相反,您看不到任何打开的窗口,并且 ShowDialog 调用挂起。