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.