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

C# FileWatcher OnCreated事件快速连续写日志报文件被占用如何解决

问题核心说明

首先明确:写入前提前检测文件占用状态的方案没有生产可行性,这类方案属于典型的TOCTOU(检查时间/使用时间)竞态问题——你检测到文件未占用的瞬间,其他线程/进程完全可能立刻锁定文件,后续写入操作依然会抛出占用异常,无法从根源规避问题。

你当前代码有两个直接问题:

  • 语法错误:Path.Combine传入了3个参数,会把日志内容message拼接到文件路径中,且方法括号未闭合
  • 并发缺陷:File.WriteAllText默认以FileShare.Read模式打开文件,不支持多线程/多进程并发写入,且OnCreated事件由线程池触发,3个及以上文件同时创建时会出现多线程并发争抢文件锁的情况,直接抛出占用异常。
可行落地方案

方案1:进程内排队+带重试的共享写入(推荐,无第三方依赖)

核心思路是先通过信号量把同进程内的写入请求串行化,从根源避免同进程多线程抢锁;打开文件时指定允许共享读写的模式,搭配指数退避重试处理其他进程占用文件的极端场景,彻底规避锁异常。

// 进程内日志写入锁,保证同进程写入请求串行执行
private static readonly SemaphoreSlim _logWriteLock = new SemaphoreSlim(1, 1);

public static async Task AppendLog(string message)
{
    string logFullPath = Path.Combine(@"c:\logs", "mylog.txt");
    const int maxRetryTimes = 5;
    int currentRetry = 0;
    int retryDelayMs = 100;

    // 等待进程内锁,避免同进程并发写入
    await _logWriteLock.WaitAsync();
    try
    {
        while (true)
        {
            try
            {
                // 以追加模式、共享读写模式打开文件,兼容跨进程并发写入场景
                using var fileStream = new FileStream(
                    path: logFullPath,
                    mode: FileMode.Append,
                    access: FileAccess.Write,
                    share: FileShare.ReadWrite,
                    bufferSize: 4096,
                    useAsync: true);
                using var writer = new StreamWriter(fileStream);
                await writer.WriteLineAsync(message);
                // 写入成功直接跳出循环
                break;
            }
            catch (IOException ex) when ((ex.HResult & 0x0000FFFF) is 32 or 33) 
            // 32=文件被进程锁定,33=文件被其他进程占用,仅针对锁异常重试
            {
                currentRetry++;
                if (currentRetry > maxRetryTimes)
                    throw; // 超过最大重试次数直接抛出,避免无限等待
                await Task.Delay(retryDelayMs);
                retryDelayMs *= 2; // 指数退避,降低重试撞锁概率
            }
        }
    }
    finally
    {
        _logWriteLock.Release();
    }
}

调用时直接在OnCreated事件中用await AppendLog(你的日志内容)即可,注意要把OnCreated事件处理方法标记为async void(事件处理方法是唯一允许使用async void的场景)。

方案2:直接使用成熟日志库(生产环境优先选)

不需要自己实现并发、锁、重试逻辑,直接引入Serilog、NLog等成熟日志组件,这些组件内部已经实现了异步写入队列、文件锁竞争处理、滚动日志切割、异常兜底等能力,只需要简单配置即可实现稳定的文件日志写入,稳定性远高于自定义实现。

不推荐方案说明

不要实现类似下面的“文件占用检测”逻辑:

// 错误示例:该检测逻辑完全无法避免并发锁异常
public static bool IsFileLocked(string filePath)
{
    try
    {
        using File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
        return false;
    }
    catch (IOException)
    {
        return true;
    }
}

该逻辑执行完成到实际发起写入操作的时间差内,文件锁状态随时可能变化,检测结果没有参考价值,依然会随机出现锁占用异常。

另外补充注意点:FileSystemWatcher的OnCreated事件在部分系统场景下会对同一个新文件触发多次,建议增加简单的文件路径去重逻辑,避免重复写入日志。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 03:15:44