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

C#控制台应用全局捕获Runtime Exception防止程序崩溃

嘿,我完全理解你从Java转C#遇到的这个困惑——Java里未捕获的运行时异常不会直接让整个程序挂掉,但C#控制台应用的默认行为确实不一样,尤其是遇到未处理的异常时会直接终止。咱们结合你的问题具体拆解下,看看怎么正确实现全局异常捕获:

先分析你当前的问题

你的错误堆栈最后指向了System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(),说明这个异常是在后台线程池线程里抛出的;而且是被TargetInvocationException包裹的InvalidDataException(来自PacketReader.ReadString方法)。你之前尝试的Main方法包裹、AppDomain的两个事件没用,主要是因为:

  • FirstChanceException只是给你一个异常发生的提前通知,它不会拦截或处理异常,异常还是会继续传播
  • AppDomain.UnhandledException在.NET Core/.NET 5+版本里,触发后程序依然会终止(这个事件只能用来记录日志,无法阻止退出)
  • 主线程的try-catch抓不到后台线程/线程池里抛出的异常

正确的全局异常处理方案

1. 主线程异常:用try-catch包裹完整逻辑(包括异步操作)

如果你的程序用了异步代码,一定要用async Main(C# 7.1及以上支持),这样才能捕获异步流程中的异常:

static async Task Main(string[] args)
{
    try
    {
        // 把所有程序入口逻辑放在这里,包括异步调用
        await StartApplicationAsync();
    }
    catch (Exception ex)
    {
        // 这里可以做日志记录、用户提示等
        Console.WriteLine($"捕获主线程异常:{ex}");
        // 自定义退出码,替代默认的错误码
        Environment.ExitCode = 1;
    }
}

2. 后台线程/线程池任务:在任务内部加try-catch

针对线程池里的任务(比如你遇到的ThreadPoolWaitCallback),必须在任务的入口处包裹try-catch,不然异常会直接导致程序终止:

// 如果用Task执行后台任务,一定要await或者在内部处理异常
await Task.Run(() => 
{
    try
    {
        // 你的后台任务逻辑,比如PacketReader相关操作
        ProcessPacket();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"后台任务异常:{ex}");
        // 这里可以做恢复逻辑,比如跳过错误数据包
    }
});

// 如果用ThreadPool.QueueUserWorkItem,同样要在回调内部加try-catch
ThreadPool.QueueUserWorkItem(state =>
{
    try
    {
        ProcessPacket(state as PacketData);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"线程池任务异常:{ex}");
    }
});

3. 处理反射调用的TargetInvocationException

你的异常是反射调用方法时抛出的,这种情况会把真实异常包装在InnerException里,你需要专门处理:

try
{
    // 你的反射调用代码
    var method = typeof(SomeClass).GetMethod("SomeMethod");
    method.Invoke(instance, parameters);
}
catch (TargetInvocationException ex)
{
    // 拿到真实的异常(你的情况是InvalidDataException)
    var realException = ex.InnerException;
    Console.WriteLine($"反射调用真实异常:{realException}");
    // 针对InvalidDataException做处理,比如跳过错误的数据包
    if (realException is InvalidDataException)
    {
        Console.WriteLine("发现无效数据包,已跳过");
    }
}

4. 最后一道防线:记录未处理的异常

虽然阻止不了程序退出,但可以用两个事件记录最后一手的异常信息:

// 处理AppDomain级别的未处理异常
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
    var ex = e.ExceptionObject as Exception;
    Console.WriteLine($"未处理的AppDomain异常:{ex}");
    // 这里可以写入日志文件,方便排查问题
};

// 处理未被观察到的Task异常(比如忘记await的Task)
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
    Console.WriteLine($"未观察到的Task异常:{e.Exception}");
    // 设置标记表示已经处理,避免程序终止(.NET Core+里默认不会终止,但还是要处理)
    e.SetObserved();
};

关于C#和Java的差异

C#的设计理念是:未处理的异常属于严重错误,应该终止程序避免不稳定状态,所以没有Java那样全局“接住所有异常让程序继续跑”的机制。但你可以通过上述方法,在每个线程/任务入口都加异常处理,尽可能捕获并处理异常,避免程序意外退出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 12:17:55