WPF应用MQTTnet订阅更新ViewModel崩溃,栈溢出无提示的排查疑问
问题背景与疑问
应用代码实现
ViewModel代码
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 } }
UI绑定代码
<TextBlock x:Name="MostRecentReceived" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding MostRecentReceipt, Mode=OneWay}"/>
全局异常处理代码
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); }
问题现象
当MQTT客户端订阅事件触发并尝试更新MostRecentReceipt属性时,应用直接崩溃:
- try语句的catch断点未触发
UnhandledExceptionHandler也未执行- 即使在VS中设置CLR异常抛出时中断,应用仍直接崩溃
后续排查发现根因是setter中的意外递归导致栈溢出(MostRecentReceipt = value应改为mostRecentReceipt = value),且栈溢出发生在PropertyChanged事件触发前。
疑问
- 为何Windows事件日志未记录栈溢出异常?
- 为何VS未给出应用因栈溢出终止的提示,也未在溢出时触发调试中断以查看栈跟踪?
问题解答
关于Windows事件日志未记录栈溢出的原因
栈溢出(StackOverflowException)属于.NET中无法被捕获的致命异常,CLR会直接终止进程,不会将其路由到常规的异常处理机制,包括AppDomain.UnhandledException事件。Windows事件日志通常只会记录那些经过CLR异常处理流程的未处理异常,而栈溢出触发的是进程的强制终止,属于操作系统层面的直接结束,因此不会被常规的事件日志条目捕获。
关于VS未触发调试中断的原因
- 栈溢出的特殊性:
StackOverflowException发生时,CLR的调用栈已经耗尽,无法执行调试器的中断逻辑——调试器需要一定的栈空间来完成中断信号的处理和上下文捕获,此时栈已经满了,没有空间执行这些操作。 - 调试器设置限制:默认情况下,VS的调试器不会在
StackOverflowException抛出时中断,因为这类异常发生时进程已经处于不可恢复的状态,调试器无法安全地获取有效的调用栈信息。即使开启了“CLR异常抛出时中断”,由于栈溢出的致命性,调试器也无法响应这个中断请求。
内容的提问来源于stack exchange,提问作者Hydrargyrum
相关产品推荐
相关产品推荐

