2016-10-28 71 views
4

我们有一个WPF繁忙窗口指示器。它在主线上使用window.ShowDialog()显示。响应Loaded事件时,将执行一个操作,并关闭该窗口,以便应用程序继续其工作。ShowDialog方法挂起而不显示窗口Dead死锁?

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

调用堆栈我看时,我打破了执行是下面的一个:

[email protected]() + 0x15 bytes 
[email protected]() + 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,1管理与ShowDialog的调用)。其他线程没有应用程序代码,线程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的专家,但我开始能够处理它,所以你可以让我也获得它的信息。

UPDATE:

增加了一些日志,我们有如下的额外信息的应用程序后:

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

我们试图用Spy ++和类似的工具找到窗口,并且据我所知,该窗口不存在,但window.ShowDialog();仍在执行。

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

+0

任何你在玩里面装载的线程/异步方法chanse? – 3615

+0

多个'Dispatcher' /线程?窗口函数的钩子?其他“低级”东西(P/Invoke)? – dymanoid

+0

你还在做什么在加载处理程序?你有没有在那里放置一个断点,看它是否被击中? – Clemens

回答

0

我发现这个问题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被关闭,你应该期望看到消息“你好”。而不是你没有看到任何窗口打开和ShowDialog调用挂起。

+0

@Hans Passant:(或者任何人)你认为这是VSTO框架中的一个bug还是这是一个无效的操作,因为不应该关闭一个打开孩子的对话框? –

1

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

大约有你的代码中的一些东西,可能是值得细看:

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

  • 目前还不清楚你在哪里创建窗口。只有在您的窗口第一次显示时才会触发Loaded事件(与例如Activated事件相反)。如果您回收您的窗口,则加载的事件不会被触发,您的后台工作人员将不会启动。

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

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

Loaded事件处理程序没有被触发,所以我猜死锁会阻止显示窗体。 –

+0

@IgnacioSolerGarcia:在什么情况下调用你的'ShowBusyWindow'方法呢?如果加载的事件没有被解雇,那么这意味着你的问题与你试图启动的后台工作无关。 –

+0

我认为工人不是问题的一部分,因为代码没有达到它。我认为有些东西阻止窗口被加载,但我不知道为什么或如何诊断它。 –

0

调用堆栈显示,你的对话框是模态消息循环等待消息。它没有被锁定或阻止。

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