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

使用并发队列写入文件时出现日志丢失问题

并发日志写入丢失问题分析与修复方案

你的日志丢失问题源于代码里几个关键设计缺陷,逐个拆解:

1. async void 导致任务失控

LogToFile 和 AppendMessage 都使用了 async void,这种写法在非UI场景下会引发两个致命问题:

  • 调用方无法等待异步任务完成,程序退出时可能还有大量日志写入任务未执行,直接被系统终止,导致日志丢失。
  • 异步过程中抛出的异常无法被上层捕获,出现静默失败你完全察觉不到。

2. 无限制并发写入引发冲突

你现在每收到一条日志就启动一个Task.Run去写文件,相当于同时有上百个线程操作同一个文件。即便用了FileMode.Append和FileShare.Write,异步写入的情况下,多个WriteAsync操作会出现执行交错——操作系统的追加模式无法保证多异步写入的原子性,最终导致部分写入被覆盖或丢失。

3. CallQueue 未起到队列作用

当前的CallQueue只是初始化了一个ConcurrentQueue,但日志写入逻辑完全没用到这个队列缓存消息,直接触发写入,队列形同虚设。另外CurrentCalls++不是原子操作,多线程下会出现计数错误,完全不可靠。


修复方案

改成单线程消费日志队列的模式,所有日志先入队,由一个后台线程负责串行写入文件,从根源避免并发写入冲突:

修复后的代码示例

public static class Logging
{
    // 用BlockingCollection实现线程安全的日志队列,自动处理阻塞等待
    private static readonly BlockingCollection<string> _logQueue = new BlockingCollection<string>();
    // 后台写入任务,确保程序运行期间持续处理队列
    private static readonly Task _writeTask;

    static Logging()
    {
        // 启动后台线程处理日志写入
        _writeTask = Task.Run(() => ProcessLogQueue());
    }

    // 对外提供的日志方法,改为async Task让调用方可按需等待
    public static async Task LogToFile(LogType type, string message)
    {
        // 标准化日志格式,加入时间戳和类型
        var logMessage = $"[{type}] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}";
        // 异步入队,避免阻塞调用方
        await Task.Run(() => _logQueue.Add(logMessage));
    }

    // 后台处理队列的方法,串行写入文件
    private static void ProcessLogQueue()
    {
        try
        {
            // 遍历队列,直到队列被标记为完成
            foreach (var message in _logQueue.GetConsumingEnumerable())
            {
                if (string.IsNullOrEmpty(message))
                    continue;

                // 单线程下用同步写入即可,避免异步额外开销
                using (var writer = new StreamWriter(FullPath, true, Encoding.ASCII))
                {
                    writer.WriteLine(message);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"日志写入失败: {ex.ToString()}");
        }
    }

    // 程序退出时调用,确保队列中所有日志都被写入
    public static void Shutdown()
    {
        _logQueue.CompleteAdding();
        _writeTask.Wait();
    }

    // 替换为你的实际日志文件路径
    private static string FullPath => @"C:\logs\app.log";
}

// 你的LogType枚举
public enum LogType
{
    Info,
    Error,
    Warning
}

关键改进点

  • 单线程串行写入:所有日志先进入线程安全的BlockingCollection队列,由一个后台线程负责写入,彻底避免并发写入的冲突和覆盖。
  • 移除async void:LogToFile改为async Task,调用方可按需等待日志入队完成;后台处理用同步写入,逻辑更简单可靠。
  • 程序退出处理:新增Shutdown方法,在程序退出前调用,确保队列中所有日志都被写入文件,不会因程序终止丢失。
  • 日志格式标准化:加入时间戳和日志类型,方便后续问题排查。

调用示例

// 循环写入700条日志
for (int n = 0; n < 700; n++)
{
    await Logging.LogToFile(LogType.Info, $"Number {n}");
}

// 程序退出前务必调用
Logging.Shutdown();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 13:36:32