0

My app needs to show balloon notifications from time to time. I do this via Dispatcher.Invoke() that performs an Action that creates a NotifyIcon, displays the balloon notification, then disposes the system tray icon.

public abstract class Foo {
    void Bar() {
        MainWindow.ShowTrayNotification(ToolTipIcon.Info, "Hello There!", "Balloon text here.");
    }
}

public partial class MainWindow : Window {

    static Dispatcher dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;

    public static void ShowTrayNotification(ToolTipIcon icon, string title, string text) {
        dispatcher.Invoke(new Action(() => {
            //anything here is not run
            UIHelper.ShowTrayNotification(icon, title, text); //static method
        }));
        //anything here is also not called if dispatcher.Invoke() is not run
    }
}

However, sometimes the Dispatcher.Invoke is not run. Logging stops right before it, and no logging happens inside the Invoke's Action.

What's weird is that when I trigger a PC shutdown, suddenly the Invoke is fired (the notification is shown) and proceeds normally (my pre-shutdown callbacks are performed).

I tried to do Debug > Windows > Threads, but the issue does not happen in Debug mode, so I can not find what is causing the UI thread to block.

What is causing this and how can I fix it?

UPDATE

I tried the following and they didn't work:

  • I changed it to BeginInvoke.

  • I added DispatcherPriority.Send.

UPDATE 2

public class UIHelper
{
    public static void ShowTrayNotification(ToolTipIcon icon, string title, string text) {
        NotifyIcon trayIcon = new NotifyIcon();
        trayIcon.Icon = MyApp.Properties.Resources.myIcon;
        trayIcon.Text = "MyApp";
        trayIcon.BalloonTipClosed += TrayNotificationClosed;
        trayIcon.Visible = true;
        trayIcon.ShowBalloonTip(5000, title, text, icon);
    }

    static void TrayNotificationClosed(object sender, EventArgs e) {
        ((NotifyIcon)sender).Visible = false;
        ((NotifyIcon)sender).Icon = null;
        ((NotifyIcon)sender).Dispose();
        sender = null;
    }
}
Obay
  • 3,135
  • 15
  • 55
  • 78
  • 1
    How about calling a dispatcher on the object itself? like `UIHelper.Dispatcher.Invoke...` Or you can try putting a `MessageBox` before the dispatcher.Invoke to see if the method even runs. That's just a suggestion. – Jakub Loksa Aug 10 '16 at 11:55
  • Dispatcher.Invoke() will deadlock when the UI thread of your app is occupied with something else. Use the Debug > WIndows > Threads debugger window to diagnose. – Hans Passant Aug 10 '16 at 13:46
  • @JakubLokša the `dispatcher.Invoke` never runs. I use the dispatcher in the `MainWindow` to make sure the `NotifyIcon` in `UIHelper` is created in the main UI thread, otherwise, it doesn't show up. – Obay Aug 11 '16 at 03:41
  • @HansPassant will using `BeginInvoke` be any different? I can just try it but the issue is very intermittent. – Obay Aug 11 '16 at 03:41
  • Is it possible that ```UIHelper.ShowTrayNotification``` locks the UI thread? – Andrei Tătar Aug 11 '16 at 06:50
  • @Andrew I added the `UIHelper` code above. It's straightforward enough and doesn't seem to lock the UI thread, does it? – Obay Aug 11 '16 at 06:59
  • Did you try to perform a !Dispatcher.CheckAccess() to make sure the Call does not come from the UI Thread anyway ? – Mohnkuchenzentrale Aug 11 '16 at 11:10
  • Also you may want to use the Application.Current.Dispatcher. Don't know if it is the same instance as the one you are using – Mohnkuchenzentrale Aug 11 '16 at 11:12
  • Most likely you're getting an exception that doesn't show. Try running the application in the debugger with break on all exceptions. My guess is `dispatcher` is sometimes `null`, and you get a silent `NullReferenceException`, or perhaps you just use the wrong dispatcher. – Luaan Aug 11 '16 at 14:59

1 Answers1

0

Try it like this :

public partial class MainWindow : Window
    {

        static Dispatcher dispatcher = System.Windows.Application.Current.Dispatcher;

        public static void ShowTrayNotification(ToolTipIcon icon, string title, string text)
        {
            if (dispatcher != null && !dispatcher.CheckAccess())
            {
                dispatcher.Invoke(() => ShowTrayNotification(icon, title, text));
                return;
            }
            UIHelper.ShowTrayNotification(icon, title, text);

        }
    }

Also see Dispatcher.CurrentDispatcher vs. Application.Current.Dispatcher

Community
  • 1
  • 1
Mohnkuchenzentrale
  • 5,745
  • 4
  • 30
  • 41
  • Looks promising, first time I heard of `CheckAccess`. Why is it needed? The docs says it `determines whether the calling thread is the thread associated with this Dispatcher.`. Why do we NOT want the calling thread to be the thread associated with `System.Windows.Application.Current.Dispatcher`? And why should `ShowTrayNotification` call itself in that case? – Obay Aug 11 '16 at 11:20
  • The Method checks whether the Call is already Coming from the specified Dispatcher. You just want to invoke the method on the dispatcher if it is not already executing on the UI Thread – Mohnkuchenzentrale Aug 11 '16 at 11:25
  • But don't I need to execute on the UI thread? I tried NOT using `Dispatcher.Invoke` before, and it would not display my notification. – Obay Aug 11 '16 at 11:26
  • The self call is there just to make sure the same method is always running on the UI Thread and you don't have to implement 2 different Methods for it – Mohnkuchenzentrale Aug 11 '16 at 11:27
  • UI Operations need to run on the UI Thread. Controls always have their own Dispatcher, but your Helper class is not a Control so you have to make sure the call runs on the UI Thread. – Mohnkuchenzentrale Aug 11 '16 at 11:29
  • I am trying your suggestion now. But could you explain how my approach was preventing the `Invoke` from being run? – Obay Aug 11 '16 at 11:33
  • I can't for sure. But my guess is that you were not using the correct Dispatcher. The use of CheckAccess is more or less philosphic. Sauce : http://stackoverflow.com/questions/12937902/correct-usage-or-not-usage-of-dispatcher-checkaccess – Mohnkuchenzentrale Aug 11 '16 at 11:44
  • I tried your suggestion, it didn't work. It still intermittently stops at `dispatcher.Invoke`, i.e. the self call is not performed. – Obay Aug 11 '16 at 11:59