【踩了一个坑】全局抓取所有线程的未捕获异常,结果偶发的 http 访问失败导致整个进程崩溃
Posted on 2026-07-24 18:59 ahfuzhang 阅读(14) 评论(0) 收藏 举报作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!
我觉得我都写了 20 多年程序了,一个简单的 API 服务器不应该出现什么难以解决的问题。
然而,这个 C# 程序莫名奇妙崩溃的问题已经一周多了,仍然在继续……
挖坑 1:出现全局未捕获的异常时,退出进程
我基于 Kestrel 写了一个自己的后端框架:https://github.com/ahfuzhang/QiWa
一开始:我担心程序中存在未捕获的异常,于是我实现了一个捕获全局的线程异常的功能:
see: 《C#: 如何从全局捕获所有线程的异常》 https://www.cnblogs.com/ahfuzhang/p/19563733
核心代码是:
AppDomain.CurrentDomain.UnhandledException += (_, eventArgs) =>
{
PrintUnhandledException("AppDomain.CurrentDomain.UnhandledException", eventArgs.ExceptionObject as Exception);
};
TaskScheduler.UnobservedTaskException += (_, eventArgs) =>
{
PrintUnhandledException("TaskScheduler.UnobservedTaskException", eventArgs.Exception);
};
因为我担心未捕获的异常会导致隐含的问题,所以未捕获的异常一律退出:
private static void PrintUnhandledException(string source, Exception? exception)
{
// 保证只输出一次,防止多线程重入
if (Interlocked.Exchange(ref _hasPrinted, 1) == 1)
{
return;
}
// todo: 在这里输出日志
const int exitCode = 99;
Environment.Exit(exitCode);
}
挖坑2: 主动 push metric 来快速采集性能数据,并采用 fire and forgot 的 Task 模式。
因为框架还在开发中,所以我希望框架自身可以快速 push metrics 数据到 VictoriaMetrics 中,便于后续实现秒级的性能监控。
做法就是自己读取 OpenTelemetry 的 metrics 对象,然后拼装为 prometheus 格式的文本,使用 http post 发送到 VictoriaMetrics 服务器端。
其中一段代码是这样的:
_ = _client!.PostAsync("", content).ContinueWith(t =>
{
buf.Dispose();
var statusCode = (int)t.Result.StatusCode;
if (t.IsFaulted)
{
ThreadLocalLogger.Current.Warn(
Field.Utf8String("_msg"u8, "post metrics fail"u8),
Field.String("url"u8, _client!.BaseAddress!.ToString()),
Field.Int64("status_code"u8, statusCode)
);
return;
}
ThreadLocalLogger.Current.Debug(
Field.Utf8String("_msg"u8, "post metrics success"u8),
Field.Int64("status_code"u8, statusCode),
Field.Int64("bytes"u8, bytes)
);
});
上面的代码想得很美好,一切皆异步,fire and forgot!
但是,当某次 http 访问失败时,就会抛出异常。特别是,测试环境机器资源紧张,特别不稳定。
于是,上面的代码就会抛出未捕获的异常。
教训
- 全局捕获 Unhandled Exception,是否因为这样的异常退出进程,这里应该做成一个明确的配置。
- 正式环境中,通过仅打日志而不退出进程来提升稳定性。
- 牢记: C# 不是 golang, C# 不是golang.
- 只要调用了 System 下面的库,就要明确的看文档了解这个库到底会抛出什么异常。然后老老实实的用 try-catch 包裹起来。
- 一点懒都不能偷
- 一个容易出问题的测试环境是好事
- 我之前在本地调试了两周,一切都很完美;好多问题部署到测试环境才暴露出来。
- 希望未来有一种混沌工程的容器,程序部署到里面就会自动模拟各种环境不稳定问题。

浙公网安备 33010602011771号