这是由于方式Microsoft.Extensions.Logging.Console\src\ConsoleLoggerProcessor.cs
被重构为这次提交 https://github.com/dotnet/runtime/commit/bb17fb55c96ed45a6323ded2b88f8b432694840d (相关公关 https://github.com/dotnet/runtime/pull/70186).
Calling logger.Log
不必立即刷新消息。如果确实如此,调用可能会花费很长时间并且阻塞的时间比预期的要长。记录器将消息传递给所有内部ILoggers
从你创建的ILoggerProvider
并按照他们的意愿处理这些消息。配置 LoggerFactorybuilder.AddConsole();
adds ConsoleLoggerProvider : ILoggerProvider
在你的情况下。
创建后ILoggerFactory
,所有配置的提供程序都被实例化,此时ConsoleLoggerProvider
构造一个新类 https://github.com/dotnet/runtime/blob/9c818a3176c8505af9ac374ad3369c5832328013/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs#L58 called ConsoleLoggerProcessor
。它负责将消息写入Console.Out
from a 它创建的后台线程 https://github.com/dotnet/runtime/blob/9c818a3176c8505af9ac374ad3369c5832328013/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProcessor.cs#L71-L76处理控制台记录器消息队列。记录器将消息推送到此队列,以便它们可以快速返回给调用者,而不会阻塞文件 IO。
When ILoggerFactory
被处置,它将处置所有创建的ILoggerProviders
这导致处置ConsoleLoggerProcesser
。处置that班级电话Thread.Join
在上面链接的消息队列处理线程上。
之前,ConsoleLoggerProcessor
的队列是通过以下方式实现的BlockingCollection<T>
。这是一个极其简化的视图:
public class ConsoleLoggerProcessor : IDisposable
{
private readonly Thread _outputThread;
private readonly BlockingCollection<string> _messageQueue = new();
public ConsoleLoggerProcessor()
{
_outputThread = new Thread(ProcessLogQueue) { IsBackground = true };
_outputThread.Start();
}
public void EnqueueMessage(string message)
{ /* snip */ }
private void ProcessLogQueue()
{
foreach (var message in _messageQueue.GetConsumingEnumerable())
{
Console.WriteLine(message);
}
}
public void Dispose()
{
_messageQueue.CompleteAdding();
_outputThread.Join(millisecondsTimeout: 1500);
}
}
处置后,BlockingCollection
已完成,可防止更多添加,but _messageQueue.GetConsumingEnumerable()
才不是yield break;
until both条件为真
这意味着_outputThread
不会终止,直到ProcessLogQueue
返回或1500
毫秒超时命中。
将此与新的实现进行比较Queue<T>
。再次极其简化以表达要点。
public class ConsoleLoggerProcessor : IDisposable
{
private readonly Thread _outputThread;
private readonly Queue<string> _messageQueue = new();
private bool _isAddingCompleted;
public ConsoleLoggerProcessor()
{
_outputThread = new Thread(ProcessLogQueue) { IsBackground = true };
_outputThread.Start();
}
public void EnqueueMessage(string message)
{ /* snip */ }
private void ProcessLogQueue()
{
while (_isAddingCompleted)
{
if (_messageQueue.Count > 0)
{
Console.WriteLine(_messageQueue.Dequeue());
}
}
}
public void Dispose()
{
_isAddingCompleted = true;
_outputThread.Join(millisecondsTimeout: 1500);
}
}
当这个实现被处理时,标志_isAddingCompleted
被设定为true
和紧密的循环ProcessLogQueue
基本上立即终止。在实际实现中,存在一些同步障碍,因此ProcessLogQueue
未将核心固定为 100%。
所以归结起来就是:
以前的记录器最多需要 1.5 秒来刷新队列中剩余的内容。阻止您的应用程序退出最多 1.5 秒,因为Thread.Join
阻塞调用线程(主线程)直到它终止。
当前的实现不会等待消息被刷新。它立即退出。