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

C#异步日志写入:确保前次I/O完成后再执行下一次写入

ASP.NET MVC日志写入实现的问题分析与解决方案

现有实现的潜在问题

你的当前实现存在几个需要注意的风险点:

  • 线程竞态问题:静态Task t没有线程同步保护,高并发场景下多个线程同时进入方法时,可能出现对t的赋值、等待逻辑混乱,导致日志顺序错乱或资源冲突。
  • 资源释放不严谨:FileStream和StreamWriter未用using语句包裹,仅依赖ContinueWith中的Close()释放资源,若WriteLineAsync抛出异常,可能出现资源泄漏。
  • 死锁隐患:在ASP.NET同步上下文环境中,直接调用t.Wait()虽当前场景大概率无问题,但后续若异步逻辑依赖同步上下文,就可能触发死锁。

关于最后一次写入的保障

必须在Web请求结束时等待未完成的日志写入任务——ASP.NET不会自动等待未完成的异步任务,若AppDomain卸载或请求结束时日志任务仍在执行,会直接导致日志丢失。

建议在Global.asax的Application_EndRequest事件中添加等待逻辑:

protected void Application_EndRequest()
{
    if (YourStaticLogClass.t != null)
    {
        YourStaticLogClass.t.Wait();
    }
}

这样能确保每个请求结束前,所有排队的日志写入操作都已完成。

优化后的实现方案

针对上述问题,优化后的代码如下:

private static readonly object _logSyncLock = new object();
private static Task _pendingLogTask = Task.CompletedTask;

public static void ErrorLog(string file, string error)
{
    lock (_logSyncLock)
    {
        _pendingLogTask.Wait();
        
        var logFilePath = Path.Combine(HttpRuntime.AppDomainAppPath, "logs", file);
        using (var fileStream = new FileStream(logFilePath, FileMode.Append, FileAccess.Write, FileShare.Read, bufferSize: 4096, useAsync: true))
        using (var streamWriter = new StreamWriter(fileStream))
        {
            _pendingLogTask = streamWriter.WriteLineAsync($"### {error}");
        }
    }
}

优化点说明:

  • 新增_logSyncLock确保多线程下日志逻辑的原子性,彻底避免竞态问题;
  • 使用using语句自动管理流资源,无论是否出现异常都能保证资源释放;
  • 将FileShare.None改为FileShare.Read,允许其他进程读取日志文件(若不需要可改回);
  • 移除ContinueWith,using会在WriteLineAsync完成后自动关闭流,简化逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 09:15:30