You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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事件触发前。

疑问

  1. 为何Windows事件日志未记录栈溢出异常?
  2. 为何VS未给出应用因栈溢出终止的提示,也未在溢出时触发调试中断以查看栈跟踪?

问题解答

关于Windows事件日志未记录栈溢出的原因

栈溢出(StackOverflowException)属于.NET中无法被捕获的致命异常,CLR会直接终止进程,不会将其路由到常规的异常处理机制,包括AppDomain.UnhandledException事件。Windows事件日志通常只会记录那些经过CLR异常处理流程的未处理异常,而栈溢出触发的是进程的强制终止,属于操作系统层面的直接结束,因此不会被常规的事件日志条目捕获。

关于VS未触发调试中断的原因

  1. 栈溢出的特殊性:StackOverflowException发生时,CLR的调用栈已经耗尽,无法执行调试器的中断逻辑——调试器需要一定的栈空间来完成中断信号的处理和上下文捕获,此时栈已经满了,没有空间执行这些操作。
  2. 调试器设置限制:默认情况下,VS的调试器不会在StackOverflowException抛出时中断,因为这类异常发生时进程已经处于不可恢复的状态,调试器无法安全地获取有效的调用栈信息。即使开启了“CLR异常抛出时中断”,由于栈溢出的致命性,调试器也无法响应这个中断请求。

内容的提问来源于stack exchange,提问作者Hydrargyrum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 19:05:02