0

I have a WPF application built using .NET 7 Windows Desktop SDK, that uses MQTTnet.

I have a ViewModel which defines some properties using INotifyPropertyChanged, and contains an MQTTnet client:

public class MyViewModel : INotifyPropertyChanged
{
    private IMqttClient Client;

    public String MostRecentReceipt
    {
        get => mostRecentReceipt;
        private set
        {
            MostRecentReceipt = value;
            OnPropertyChanged();
        }
    }
    private String mostRecentReceipt = String.Empty;

    public event PropertyChangedEventHandler? PropertyChanged;

    // Create the OnPropertyChanged method to raise the event
    // The calling member's name will be used as the parameter.
    protected void OnPropertyChanged([CallerMemberName] String name = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }

    // Connection setup for MQTT client
    public async Task Start()
    {
        var mqttClientOptions = new MqttClientOptionsBuilder()
            .WithTcpServer(MqttBroker)
            .Build();

        Client.ApplicationMessageReceivedAsync += HandleMqttEvent;

        await Client.ConnectAsync(mqttClientOptions);

        var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder();

        foreach(var topic in subscribedTopics)
        {
            mqttSubscribeOptions = mqttSubscribeOptions.WithTopicFilter(topic);
        }

        await Client.SubscribeAsync(mqttSubscribeOptions.Build());
    }


    // Event handler for incoming MQTT messages
    private async Task HandleMqttEvent(MqttApplicationMessageReceivedEventArgs message)
    {
        var topic = message.ApplicationMessage.Topic;
        var topicSuffix = topic.Substring(topic.LastIndexOf('/') + 1);
        try
        {
            MostRecentReceipt = topicSuffix;
        }
        catch (Exception e)
        {
            var msg = e.Message;
        }

        // Other async code here
    }
}

The MostRecentReceipt property of the ViewModel is bound to a UI TextBlock in MainWindow via a standard OneWay binding, which should be updated via the INotifyPropertyChanged implementation:

<TextBlock x:Name="MostRecentReceived" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding MostRecentReceipt, Mode=OneWay}"/>

I have a handler registered for AppDomain.UnhandledException:

    public App()
    {
        AppDomain currentDomain = AppDomain.CurrentDomain;
        currentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionHandler);
    }

    public static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
    {
        Exception e = (Exception)args.ExceptionObject;
        Console.WriteLine(e.Message);
    }

My problem is that whenever the MQTT client subscription event fires and tries to update the MostRecentReceipt property, my app completely crashes. A breakpoint in the catch statement for the try around the MostRecentReceipt assignment is never hit. The UnhandledExceptionEvent handler is not hit either. Checking “CLR Exceptions” in the VS debugger exception handling, to break on throw rather than breaking on unhandled only, does nothing - the app still crashes instead of breaking at the guilty line of code.

It looks like a thread dispatching issue, since it happens when I try to set a property which is bound to the UI. But other questions I've looked at say that WPF bindings to INotifyPropertyChanged interfaces are supposed to automatically take care of dispatching the update onto the correct UI thread.

What do I need to implement to properly catch these exceptions, rather than have an app crash?

Why isn't the property change being automatically dispatched onto the UI thread? How do I fix it from the context of a ViewModel, which has no access to the actual View and can't access any of the UI control Dispatchers?

Update: The root cause of the app crashes has been identified as a stack overflow caused by accidental recursion, and nothing to do with thread dispatching. AFAIK there is no way to validly handle a StackOverflowException, so that answers the question about properly catching the exception and why the property change wasn't being dispatched (the stack overflow happens before PropertyChanged event fires). No dispatching changes are needed, so the question about dispatching from a ViewModel is moot.

However, I still don't know why I didn't get any Windows Event Log messages explaining that stack overflow exceptions were occurring, or any feedback from Visual Studio explaining that the app had terminated due to a stack overflow (e.g. a dialogue box or debug output), or a debugger break when the stack overflow occurred with an opportunity to see the stack trace. I will accept an answer that resolves these mysteries.

Hydrargyrum
  • 3,378
  • 4
  • 27
  • 41

1 Answers1

0

The solution had nothing to do with thread dispatch or async.

The actual problem is that the MostRecentReceipt property setter accidentally recurses instead of setting the private mostRecentReceipt field value. (This is a good reason not to have public and private members which differ only in case.) This obviously causes a stack overflow exception, which also explains why the AppDomain.UnhandledException didn't fire - IIUC, that event doesn't fire for StackOverflowException because nothing can handle those.

Hydrargyrum
  • 3,378
  • 4
  • 27
  • 41