Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dispose() does not work reliably when called during application shutdown #122

Open
ssrmm opened this issue Dec 2, 2024 · 0 comments
Open

Comments

@ssrmm
Copy link

ssrmm commented Dec 2, 2024

It looks like the implementation in #93 was not fully correct. I'm seeing the following problems related to calling TaskbarIcon.Dispose() explicitly during application shutdown:

  1. Explicit dispose may deadlock itself with the one triggered by TaskbarIcons Application.Exit handler.
  2. Dispose may throw TaskCanceledException when called explicitly during application shutdown. Note that this second problem already happened before the mentioned PR as well.

Both of these only happen sporadically. Unfortunately I am neither able to reproduce them in a minimal example nor am I able to share the code of the actual application. So I've written down my observations below..

In my own code, I've now removed the explicit TaskbarIcon.Dispose() entirely, relying on the dispose from Application.Exit, and suppressing the dispose-releated analyzer warning instead. This works for me because application and tray icon lifetime are coupled in my case. But that may not be the case for other applications, so a general solution to the problem still makes sense I think.

Deadlock

Here is a screenshot from Visual Studios Parallel Stacks view showing the problem. From what I can tell, the following happens:

  1. A thread other than the UI-Thread calls Application.Current.Dispatcher.BeginInvoke(() => Application.Current.Shutdown()) (or similar). This will queue the shutdown operation on the UI thread.
  2. The same or yet another threads calls TaskbarIcon.Dispose().
  3. TaskbarIcon.Dispose() takes the lock in TaskbarIcon.cs#L1105
  4. TaskbarIcon.Dispose() queues the unsubscribe from Application.Exit on the dispatcher and waits until it finished executing (Application.Current.Dispatcher.Invoke(() => Application.Current.Exit -= OnExit)).
  5. The dispatcher executes the Application.Current.Shutdown() from earlier on the UI thread and invokes Application.Exit handlers as part of that procedure
  6. TaskbarIcon.OnExit() gets called and invokes TaskbarIcon.Dispose().
  7. The second TaskbarIcon.Dispose() call tries to acquire the lock which is currently held by the other thread that caused the first invocation.
  8. The UI thread is blocked, will never get to the unsubscribe from Application.Exit and so the other thread will never return form the Dispatcher.Invoke() call.
    image

TaskCanceledException

Here I don't really have any concrete information as to why this exception happens. My best guess is that the application shutdown has already progressed far enough that the application dispatcher is not functional anymore and cannot be called.

Exception Info: System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Windows.Threading.DispatcherOperation.Wait(TimeSpan timeout)
   at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherOperation operation, CancellationToken cancellationToken, TimeSpan timeout)
   at System.Windows.Threading.Dispatcher.Invoke(Action callback, DispatcherPriority priority, CancellationToken cancellationToken, TimeSpan timeout)
   at System.Windows.Threading.Dispatcher.Invoke(Action callback)
   at Hardcodet.Wpf.TaskbarNotification.TaskbarIcon.Dispose(Boolean disposing) in D:\a\1\s\src\NotifyIconWpf\TaskbarIcon.cs:line 1113
   at Hardcodet.Wpf.TaskbarNotification.TaskbarIcon.Dispose() in D:\a\1\s\src\NotifyIconWpf\TaskbarIcon.cs:line 1074
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant