Метод ShowDialog зависает, не показывая окно ¿Deadlock?
У нас есть индикатор занятости окна 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, управляемый 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, но я начинаю справляться с этим, поэтому вы можете попросить меня получить информацию и о нем.
ОБНОВИТЬ:
После добавления некоторых журналов в приложение мы получаем следующую дополнительную информацию:
Проблема в том, что метод dispatcher.Invoke(new Action(window.Close));
вызывается и выполняется без исключения, кроме метода window.ShowDialog();
не возвращается.
Мы попытались найти окно с помощью Spy++ и подобных инструментов, и, насколько я могу судить, окно не там, а window.ShowDialog();
продолжает выполнять.
Я надеюсь, что это даст вам некоторое представление о том, что происходит.
3 ответа
Год спустя я обнаружил причину проблемы, следующий код демонстрирует концепцию происходящего:
Подводя итог (из-за состояния гонки в реальном коде), есть 3 окна с отношением родитель-потомок (A ->B->C), и код закрывает B. Это работает в приложении WPF (и C получает закрыто тоже) но в надстройке VSTO не работает и зависает, B никогда не покидает метод ShowDialog:
Создайте новый проект VSTO для Office Word 2010 и вставьте следующий код (не уверен, что произойдет, если вы настроите другую версию 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 зависает.
Может быть, это тупик или нет, но у нас нет достаточно информации, чтобы начать с (например, что происходит внутри 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));
}
}
}
Стеки вызовов показывают, что ваш диалог находится в модальном цикле сообщений, ожидая сообщений. Это не заблокировано или заблокировано.
Как указывает Дирк, событие Loaded не всегда вызывается. Источником этого может быть одна из двух вещей, которые он предлагает.
Обходным путем для этого может быть явный вызов onLoaded в BusyIndicatorImpl.
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();
}