7

We have a WPF busy window indicator. It is shown on the main thread using a window.ShowDialog(). On response to the Loaded event an action is executed and the window is closed so the application continues its work.

The window.ShowDialog() seems to hang from time to time (very rarely) without showing the dialog and the Loaded event is not fired so the application hangs. The related code is the following one:

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;
}

When the application hangs I can see the instruction pointer on the window.ShowDialog() method but the window is not visible on the application and the backgroundWorker has not been started yet so my guess is that the OnLoaded event has not been raised.

The application is not really hung as it redraws correctly but you can't click anywhere on the screen. As a side weird effect when the app hangs it dissapears from the task bar on Windows 7.

The callstack I see when I break the execution is the following one:

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#

As others said before it looks like a deadlock so I got a dump with Visual Studio and ran some tools on it looking for the deadlock. This is the information of the threads the application has running:

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) 

There is only one thread with app code (the 0, managed 1 with the ShowDialog call). The other threads do not have application code and the thread 15 (managed 10) is the only one with some .Net code.

Looking at the thread 15 (managed 10) as it is the one with a lock I see the following callstack:

[[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]] 

This callstack looks like a timer waiting to be fired to me but maybe I'm wrong about this interpretation so I don't know how to continue with this.

I can provide any information that you need by request. I'm not an expert on WinDbg but I'm starting to be able to handle it so you can ask me to get information with it as well.

UPDATE :

After adding some logs to the application we have the following extra information:

The problem is that the method dispatcher.Invoke(new Action(window.Close)); is called and it executes without throwing an exception but the method window.ShowDialog(); does not return.

We have tried to find the window with Spy++ and similar tools and as far as I can say the window is not there but the window.ShowDialog(); keeps executing.

I hope this gives you some insight about what is going on.

Ignacio Soler Garcia
  • 20,097
  • 26
  • 114
  • 195
  • Any chanse you are playing with threads/async methods inside onLoaded? – 3615 Oct 28 '16 at 11:36
  • Multiple `Dispatcher`s / threads? Any hooks on window functions? Other "low-level" stuff (P/Invoke)? – dymanoid Oct 28 '16 at 11:46
  • What else are you doing in the Loaded handler? Did you put a breakpoint there to see if it's ever hit? – Clemens Oct 28 '16 at 11:46
  • Inside the onLoaded we start a background worker but it looks that it was never started. – Ignacio Soler Garcia Oct 28 '16 at 11:52
  • @dymanoid: multiple threads. Native and managed. It is a managed word Addin. I can see hooks from other stuff like video drivers, etc but our application does not have hooks. – Ignacio Soler Garcia Oct 28 '16 at 11:54
  • Is there a way to check if the main thread is doing something else? Shouldn't it be visible in the callstack of the main thread? – Ignacio Soler Garcia Oct 28 '16 at 12:29
  • @IgnacioSolerGarcia I think you are having deadlock because main thread waits for some background thread, while background thread wait the main one(for example background thread calls Dispatcher to do something with UI). I don't think you will see this in main thread call stack, but when it hangs you can click Pause to enter debug and see what's each of your's threads is currently doing. – 3615 Oct 28 '16 at 12:43
  • This indeed looks like a deadlock (`ShowDialog()` waits for the second window to close, but the second window invokes the event handler `onLoaded` on the first window, which is still waiting for `ShowDialog()` to return). Have you tried removing that handler? And what are you doing in the `onLoaded` handler? – bassfader Oct 28 '16 at 14:38
  • @3615: question updated with much more information and a bounty .... – Ignacio Soler Garcia Nov 21 '16 at 19:12
  • @Clemens: question updated with much more information and a bounty .... – Ignacio Soler Garcia Nov 21 '16 at 19:12
  • Question is what happens inside BackgroundWorker, because could happen that code causes hang. Especially if it is modifing UI presented elements in ViewModel. Please also check (using Debug.WriteLine) if onLoad is not really called - just put debug message before backgoundworker.RunWorkerAsync(); – smartobelix Nov 22 '16 at 07:17
  • @smartobelix: the backgroundworker is not started and I have no thread running the code of the action so I think we can be pretty sure that the worker has not been started – Ignacio Soler Garcia Nov 22 '16 at 08:33
  • What happens if you remove window.Loaded? Is then window shown correctly? What I see as potential problem is that you don't have parent set in ShowDialog. Potentially it can cause window is UNDER window of application. We had such issues in the past. Please try as temporary test show it withou this event and fire background worker from button on this form. (Just to see where is the problem) – smartobelix Nov 22 '16 at 08:39
  • This is a problem that happens from time to time (maybe once a day in a set of 1200 machines) so it's not that easy to verify if something fixes the problem or not. Having a window under the main one would fire the Loaded event anyway, right? – Ignacio Soler Garcia Nov 22 '16 at 09:28
  • So when is the `window` created? Are you sure that it hasn't been loaded before or that it is not already shown? It would really help a lot (also for you own analysis) if you could create a small sample to reproduce the issue. – Dirk Vollmar Nov 22 '16 at 10:13
  • When don't know how to reproduce the bug so creating a sample is really difficult. What we have is some dumps of the bug, the source code and sometimes a hung machine with a remote debugger attached. We don't see any thread running the background worker action code and when the background worker ends the window is closed so my guess is that is it not shown (and you cannot see the window, maybe it is in the back of the main app but you can not see it nor using alt tab). – Ignacio Soler Garcia Nov 22 '16 at 10:24
  • 1
    The only conclusion that can be drawn from the provided info, with 100% certainty, is that it is **not** deadlock. The "not shown" guess isn't very helpful, it is definitely busy displaying the dialog. We can't see how the BGW got created, RunWorkerAsync() can throw an exception when it is still busy. And on the 64-bit version of Win7 that exception can get lost without trace if your app runs in 32-bit mode. Repro by intentionally throwing an exception before the RunWorkerAsync() call, run it on one of the machines that have this problem. – Hans Passant Nov 22 '16 at 10:58
  • It is hard to imagine that ShowDialog is called but window is not shown. In code it is not visible how and when is backgroundWorker initialized. Are you re-using the same background worker several times? Maybe there is something what could be origin of the problem? However documentation says second call to RunWorkeAsync should throw exception, not cause hang. – smartobelix Nov 22 '16 at 12:32
  • @HansPassant: I've added more information. Does you sentence ' Using Invoke() is by itself quite dangerous' still apply? We are planning to remove it in the next version to see what happens. – Ignacio Soler Garcia Dec 21 '16 at 07:54

3 Answers3

3

I found the cause of the issue one year latter, the following code shows a proof of concept of what is happening:

Summarizing (due to a race condition in the real code) there are 3 windows with a parent - child relation (A-->B-->C) and the code is closing B. This works on a WPF application (and C gets closed too) but in a VSTO add-in does not work and hangs, B never leaves the ShowDialog method:

Create a new VSTO project for Office Word 2010 and paste the following code (not sure about what happens if you target a different Office version):

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
    }
}

As the window1 gets closed you should expect to see the message "Hello". Instead of that you don't see any window open and the ShowDialog call gets hang.

Ignacio Soler Garcia
  • 20,097
  • 26
  • 114
  • 195
  • @Hans Passant: (or anyone) do you think that this is a bug in the VSTO framework or is this an invalid operation as one should not close a dialog that has a child open? – Ignacio Soler Garcia Jan 11 '18 at 13:55
  • I'm having nearly the same problem, in a normal WPF application. If the owner of a modal window is closed, the modal window ShowModal() never returns. In my case, the owner is a normal window. So I don't believe that's an VSTO problem but a WPF Window thing. – Lumo May 24 '18 at 09:20
  • I'm pretty sure it's not as I had done extensive tests on this topics and anytime I tried to close a parent dialog all the child got closed. Which version of WPF are your running? – Ignacio Soler Garcia May 24 '18 at 13:17
  • We are using .NET 4.7.1. Didn't create a simple example yet. But I recently had this problem and I stumbled upon your question when searching for it on google. We closed a window which was the parent of the modal dialog and then ShowModal never returned. Maybe that only happens in certain circumstances ... we have a complicated application so it's hard to say. But we solved the problem by making sure that a parent is never closed until the modal child closes. – Lumo Jun 05 '18 at 09:23
1

Might be a deadlock or not, but we don't have enough information to start from (e.g. what's happening insided the Loaded event handler, or in what context things are called).

There are some things about your code that might be worth a closer look:

  • You don't seem to set the owner of the modal dialog. In the worst case this may result in your modal dialog being shown in the background where it may be hard or impossible to dismiss.

  • It is unclear where you create the window. The Loaded event will only be fired the first time your window is shown (in contrast to e.g. the Activated event). If you recycle your window, then the loaded event is not triggered and your background worker won't be started.

Here is a very basic working WPF example consisting of a main window with a boackground worker and a modal progress dialog. I'd try to start from here to try to isolate the problem.

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));
        }
    }
}
Dirk Vollmar
  • 161,833
  • 52
  • 243
  • 303
  • The Loaded event handler is not fired so I guess the deadlock prevents the form from being shown. – Ignacio Soler Garcia Nov 22 '16 at 09:43
  • @IgnacioSolerGarcia: In what context is your `ShowBusyWindow` method called then? If the loaded event is not fired then this would mean your problem has nothing to do with the background worker you are trying to start. – Dirk Vollmar Nov 22 '16 at 09:49
  • I think that the worker is not part of the problem as the code does not reach it. I think that something is preventing the Window to be loaded but I don't know why or how to diagnose it. – Ignacio Soler Garcia Nov 22 '16 at 10:02
  • @Ignacio, food for thought... I had a similar issue where a `OpenFileDialog.Show()` would hang my WPF application for no obvious reason, and it would not throw an error. It'd just hang there until I'd "end task" on it. Turned out the application would hang when `OpenFileDialog.Show()` was called while the `InitialDirectory` was a network location no longer available at the time of calling `OpenFileDialog.Show()`. I don't explicitly set `InitialDirectory` in my code, but Windows somehow keeps track of the last location you been to, and it sets InitialDirectory to that when the dialog is called. – blaze_125 Nov 22 '16 at 20:31
0

The callstack shows that your dialog is in the modal message loop waiting for messages. It's not locked or blocked.

As Dirk points out, the Loaded event is not always raised. The source for that might be one of the two things he suggests.

A workaround for that can be to call onLoaded explicitly in BusyIndicatorImpl.

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

Alternativly, you can post it to your modal dialog.

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