并行使用 ILogger

Using ILogger in parallel

在以下代码中,程序在日志完全刷新之前终止:

    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();
      }
    }

尽管应该打印 1.000 条日志语句,但只有以下内容被刷新:

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();
  }
}

主要区别:

如果您销毁服务提供者,它将销毁所有单例服务并刷新记录器。

也没有必要使用Parallel.ForEach。事实上,将 Parallel.ForEachasync`await` 一起使用是一种代码味道。几乎总是错的。

而且,由于您标记了问题 .NET 5.0,您可以将代码更改为:

class Program
{
    static async Task Main()
    {
        var count = -1;
        var tasks = new ConcurrentBag<Task>();

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

        for (var i = 0; i < 1000; i++)
        {
            tasks.Add(Task.Run(() => logger.LogInformation(Interlocked.Increment(ref count).ToString())));
        };

        await Task.WhenAll(tasks);
    }
}