【问题标题】:ShowDialog method hangs without showing the window ¿Deadlock?ShowDialog 方法挂起而不显示窗口¿死锁?
【发布时间】:2016-10-28 11:31:16
【问题描述】:

我们有一个 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;
}

当应用程序挂起时,我可以在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#

正如其他人之前所说,它看起来像死锁,所以我使用 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) 

应用程序代码只有一个线程(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]] 

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

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

更新:

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

问题是方法dispatcher.Invoke(new Action(window.Close));被调用并且它执行时没有抛出异常但是方法window.ShowDialog();没有返回。

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

我希望这能让您对正在发生的事情有所了解。

【问题讨论】:

  • 你正在使用 onLoaded 中的线程/异步方法玩什么?
  • 多个Dispatchers / 线程?窗口函数有什么钩子吗?其他“低级”的东西(P/Invoke)?
  • 您在 Loaded 处理程序中还做了什么?您是否在此处设置了断点以查看它是否被命中?
  • 在 onLoaded 内部我们启动了一个后台工作器,但它看起来从未启动过。
  • 从提供的信息中可以得出的唯一结论是,100% 确定的是,它不是死锁。 “未显示”的猜测不是很有帮助,它肯定正忙于显示对话框。我们看不到 BGW 是如何创建的,RunWorkerAsync() 可以在它仍然忙的时候抛出异常。在 64 位版本的 Win7 上,如果您的应用程序在 32 位模式下运行,该异常可能会丢失而无影无踪。通过在 RunWorkerAsync() 调用之前故意抛出异常来重现,在有此问题的机器之一上运行它。

标签: .net wpf freeze showdialog message-pump


【解决方案1】:

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

总结(由于实际代码中的竞争条件)有 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
    }
}

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

【讨论】:

  • @Hans Passant:(或任何人)你认为这是 VSTO 框架中的一个错误还是这是一个无效操作,因为不应关闭一个打开了子项的对话框?
  • 在普通的 WPF 应用程序中,我遇到了几乎相同的问题。如果模态窗口的所有者关闭,则模态窗口 ShowModal() 永远不会返回。就我而言,所有者是一个普通的窗口。所以我不认为这是 VSTO 问题,而是 WPF Window 问题。
  • 我很确定这不是因为我已经对这个主题进行了广泛的测试,而且每当我试图关闭父对话框时,所有的孩子都被关闭了。您正在运行哪个版本的 WPF?
  • 我们使用的是 .NET 4.7.1。还没有创建一个简单的示例。但我最近遇到了这个问题,我在谷歌上搜索时偶然发现了你的问题。我们关闭了一个窗口,它是模态对话框的父窗口,然后 ShowModal 再也没有返回。也许这只发生在某些情况下……我们有一个复杂的应用程序,所以很难说。但是我们通过确保在模态子级关闭之前永远不会关闭父级来解决问题。
【解决方案2】:

可能是死锁,但我们没有足够的信息来开始(例如,Loaded 事件处理程序内部发生了什么,或者在什么上下文中调用了事物)。

您的代码有一些值得仔细研究的地方:

  • 您似乎没有设置模态对话框的所有者。在最坏的情况下,这可能会导致您的模态对话框显示在背景中,很难或不可能将其关闭。

  • 不清楚您在哪里创建窗口。 Loaded 事件只会在您的窗口第一次显示时触发(与 Activated 事件相反)。如果你回收你的窗口,那么加载的事件不会被触发,你的后台工作器也不会被启动。

这是一个非常基本的工作 WPF 示例,包括一个带有后台工作程序的主窗口和一个模式进度对话框。我会尝试从这里开始尝试隔离问题。

MainWindow.xaml

<Window x:Class="ProgressBarSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="200"
        Height="100"
        mc:Ignorable="d">
    <Grid>
        <Button Click="OnButtonClick">Start</Button>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace ProgressBarSample
{
    public partial class MainWindow
    {
        private BackgroundWorker _backgroundWorker;
        private ProgressWindow _progressWindow;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void OnButtonClick(object sender, RoutedEventArgs e)
        {
            _progressWindow = new ProgressWindow { Owner = this };
            _backgroundWorker = new BackgroundWorker { WorkerReportsProgress = true };
            _backgroundWorker.DoWork += OnWorkerDoWork;
            _backgroundWorker.RunWorkerCompleted += OnWorkerRunWorkerCompleted;
            _backgroundWorker.ProgressChanged += OnWorkerProgressChanged;
            _backgroundWorker.RunWorkerAsync();
            _progressWindow.ShowDialog();
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            _progressWindow.ProgressValue = e.ProgressPercentage;
        }

        private void OnWorkerRunWorkerCompleted(
            object sender, 
            RunWorkerCompletedEventArgs e)
        {
            _progressWindow.Close();
            MessageBox.Show("Done");
        }

        private void OnWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            int initialValue = 100;
            for (int i = 0; i < initialValue; i++)
            {
                Thread.Sleep(50);
                _backgroundWorker.ReportProgress(i);
            }
        }
    }
}

ProgressWindow.xaml

<Window x:Class="ProgressBarSample.ProgressWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="Progress"
        Width="300"
        Height="100"
        mc:Ignorable="d">
    <Grid>
        <ProgressBar Maximum="100" Minimum="0"
                     Value="{Binding ProgressValue}" />
    </Grid>
</Window>

ProgressWindow.xaml.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace ProgressBarSample
{
    public partial class ProgressWindow : INotifyPropertyChanged
    {
        private double _progressValue;

        public ProgressWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        public double ProgressValue
        {
            get { return _progressValue; }
            set
            {
                _progressValue = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(
            [CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

【讨论】:

  • 未触发 Loaded 事件处理程序,所以我猜死锁会阻止显示表单。
  • @IgnacioSolerGarcia:那你的ShowBusyWindow 方法是在什么情况下调用的?如果未触发加载的事件,则这意味着您的问题与您尝试启动的后台工作人员无关。
  • 我认为工人不是问题的一部分,因为代码没有到达它。我认为某些东西正在阻止加载窗口,但我不知道为什么或如何诊断它。
  • @Ignacio,深思熟虑...我有一个类似的问题,OpenFileDialog.Show() 会无缘无故挂起我的 WPF 应用程序,并且不会引发错误。它会一直挂在那里,直到我“结束任务”。结果发现,当调用OpenFileDialog.Show() 时应用程序将挂起,而InitialDirectory 是在调用OpenFileDialog.Show() 时不再可用的网络位置。我没有在我的代码中显式设置InitialDirectory,但Windows 会以某种方式跟踪您上次到过的位置,并在调用对话框时将 InitialDirectory 设置为该位置。
【解决方案3】:

调用堆栈显示您的对话框处于模式消息循环中等待消息。它没有被锁定或阻止。

正如 Dirk 指出的,Loaded 事件并不总是被引发。其来源可能是他建议的两件事之一。

解决方法是在 BusyIndi​​catorImpl 中显式调用 onLoaded。

private void BusyIndicatorImpl(Window window, RoutedEventHandler onLoaded)
{
    onLoaded();
    window.ShowDialog();
}

或者,您可以将其发布到您的模态对话框中。

private void BusyIndicatorImpl(Window window, RoutedEventHandler onLoaded)
{
    window.Dispatcher.InvokeAsync(() => onLoaded(window, null));
    window.ShowDialog();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-06
    • 1970-01-01
    相关资源
    最近更新 更多