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

C#并行场景使用ILogger时日志未完全刷出如何处理?

控制台日志提前终止输出问题及解决方案

问题复现

如下代码中,程序会在日志完成全部刷出操作前就终止运行:

class Program
{
  static void Main(string[] args)
  {
    var count = -1;
    var tasks = new ConcurrentBag<Task>();

    var services = new ServiceCollection();
    services.AddLogging(configure => configure.AddConsole());
    var serviceProvider = services.BuildServiceProvider();
    var logger = serviceProvider.GetService<ILogger<Program>>();
    if (logger is null)
      throw new NullReferenceException();

    Parallel.ForEach(Enumerable.Range(0, 1000), i =>
    {
      tasks.Add(Task.Run(() => logger.LogInformation(Interlocked.Increment(ref count).ToString())));
      //tasks.Add(Task.Run(() => Console.WriteLine(Interlocked.Increment(ref count).ToString())));
    });

    Task.WhenAll(tasks).Wait();
  }
}

按理应当打印1000条日志语句,但实际仅刷出了少量内容,运行输出示例如下:

info: Net5.Program[0]
      6
info: Net5.Program[0]
      2
info: Net5.Program[0]
      5
info: Net5.Program[0]
      1
info: Net5.Program[0]
      7
info: Net5.Program[0]
      4
info: Net5.Program[0]
      3
info: Net5.Program[0]
      0
info: Net5.Program[0]
      8
info: Net5.Program[0]
      10

C:\Users\MyName\source\repos\ConsoleApp\Net5\bin\Debug\net5.0\Net5.exe (process 28048) exited with code 0.
Press any key to close this window . . .

但如果使用代码中被注释掉的Console.WriteLine,所有行都会被正常打印,由此推测控制台日志器的底层为异步实现。

提问:不借助手动延迟的前提下,如何确保所有日志语句都被正确等待执行完成?

解决方案

提问者最终采用的实现代码如下,可完美解决该问题:

class Program
{
  static void Main(string[] args)
  {
    var count = -1;

    var services = new ServiceCollection();
    services.AddLogging(configure => configure.AddConsole());
    using var serviceProvider = services.BuildServiceProvider();
    var logger = serviceProvider.GetRequiredService<ILogger<Program>>();

    var tasks = Enumerable.Range(0, 50000)
      .AsParallel()
      .Select(_ => Task.Run(() => logger.LogInformation(Interlocked.Increment(ref count).ToString())));

    Task.WhenAll(tasks).Wait();
  }
}

核心优化点

  • 给ServiceProvider添加了using声明,会在程序退出前自动释放ServiceProvider
  • 使用GetRequiredService获取ILogger实例,无需做空值检查
  • 使用Enumerable.Range(...).AsParallel.Select(...)替代Parallel.ForEach,全程基于Task实现并行逻辑

原理解释

.NET内置的Console日志提供程序采用了异步队列设计:调用Log*系列方法时,日志内容只是被提交到内部的待处理队列,不会同步阻塞等待写入控制台。默认情况下程序退出时不会等待队列处理完成,就会直接终止进程,导致日志丢失。
而ServiceProvider被释放时,会触发所有托管服务的清理逻辑,其中就包含Console日志提供程序的刷出逻辑,会等待队列中所有未处理的日志全部写入控制台后,释放流程才会结束,因此可以保证所有日志都正常输出。

内容的提问来源于stack exchange,提问作者Kristoffer Lerbæk Pedersen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 12:39:03