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
相关产品推荐
相关产品推荐

