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

Windows服务FileSystemWatcher触发InputOnChanged时崩溃求助

问题排查与修复方案

核心问题分析

你的Windows服务在监控目录添加文件时崩溃,且InputOnChanged中的日志无法写入,主要源于以下几点:

  1. FileSystemWatcher事件特性:Changed事件会因文件写入的中间操作(如临时缓存、多次保存)触发多次,且首次触发时文件可能仍被写入进程锁定,甚至已被意外删除,导致后续文件操作报错。
  2. 异常捕获不完整:当前代码仅捕获了文件处理逻辑内的异常,方法开头的日志写入等操作若抛出异常,会直接导致服务崩溃且无日志记录。
  3. 文件操作时机问题:在未确保文件完全写入、释放锁定的情况下就执行复制、读取操作,极易引发「文件不存在」或「文件被占用」异常。

修复步骤与代码优化

1. 完整捕获所有异常

将InputOnChanged的全部逻辑包裹在顶层try-catch中,确保任何环节的异常都能被捕获并记录,避免服务崩溃。同时在捕获时记录完整堆栈信息,便于定位问题。

2. 处理文件锁定与延迟触发

添加短暂延迟等待文件完全写入,且在文件操作前再次检查文件存在性;优先使用Created事件替代Changed事件(更适合监控新添加的文件)。

3. 确保目录存在

在执行文件复制、写入前,先检查并创建目标目录,避免因目录不存在引发异常。

修改后的代码示例

protected void InputOnChanged(object source, FileSystemEventArgs e)
{
    // 顶层捕获所有异常,防止服务崩溃
    try
    {
        if (e.ChangeType != WatcherChangeTypes.Changed)
            return;

        eventLog.WriteEntry($"Change Detected - File {e.Name}", EventLogEntryType.Information);

        // 延迟1秒,等待文件完全写入并释放锁定
        System.Threading.Thread.Sleep(1000);

        // 再次校验文件存在性
        if (!File.Exists(e.FullPath))
        {
            eventLog.WriteEntry($"{e.Name} was not accessible at processing time", EventLogEntryType.Error);
            return;
        }

        try
        {
            fileWatcherService.EnableRaisingEvents = false;
            eventLog.WriteEntry("Starting process for file: " + e.Name);

            // 备份文件处理:确保目标目录存在
            string backupDir = Path.Combine(@"\\XXXXXX\XXXXXX\XXXXXXXX\XXXXXXXXXX XXX XXXXXXXXX\XXX\XXXX\XXXX\BackupFiles");
            Directory.CreateDirectory(backupDir);
            string backupFilePath = Path.Combine(backupDir, GetBackupFileName(e.Name));
            File.Copy(e.FullPath, backupFilePath, false);

            // 格式化文件处理:确保目标目录存在
            string formattedDir = Path.Combine(@"\\XXXXXX\XXXXXX\XXXXXXXX\XXXXXXXXXX XXX XXXXXXXXX\XXX\XXXX\XXXX\FormattedFiles");
            Directory.CreateDirectory(formattedDir);
            string formattedFilePath = Path.Combine(formattedDir, GetFormattedFileName(e.Name));
            
            List<string> lines = System.IO.File.ReadAllLines(e.FullPath).ToList();
            File.WriteAllText(formattedFilePath, CSVFormatService.FormatLines(lines));

            // 删除原文件
            File.Delete(e.FullPath);
            eventLog.WriteEntry($"Successfully processed and deleted {e.FullPath}", EventLogEntryType.Information);
        }
        catch (IOException ioEx)
        {
            // 单独捕获IO异常,更精准定位文件操作问题
            eventLog.WriteEntry($"IO error processing {e.Name}: {ioEx.Message}\nStack Trace: {ioEx.StackTrace}", EventLogEntryType.Error);
        }
        catch (Exception ex)
        {
            eventLog.WriteEntry($"Service exception processing {e.Name}: {ex.Message}\nStack Trace: {ex.StackTrace}", EventLogEntryType.Error);
        }
        finally
        {
            fileWatcherService.EnableRaisingEvents = true;
        }
    }
    catch (Exception ex)
    {
        // 极端情况:若eventLog实例不可用,使用应急日志写入
        using (var emergencyLog = new EventLog("Application"))
        {
            emergencyLog.Source = "YourCustomSourceName"; // 替换为你的自定义事件源名称
            emergencyLog.WriteEntry($"Critical error in InputOnChanged: {ex.Message}\nStack Trace: {ex.StackTrace}", EventLogEntryType.Error);
        }
    }
}

额外排查建议

  • 权限校验:确认服务运行的域用户对监控目录、备份目录、格式化目录拥有读、写、删除权限,包括共享目录的NTFS权限和共享权限。
  • 事件日志排查:在事件查看器的「系统」日志中查找服务崩溃的事件(事件ID通常为7034),获取崩溃的详细调用栈。
  • 替换事件类型:将FileSystemWatcher的监听事件从Changed改为Created,该事件在文件完全创建完成后触发,更适合处理新添加的文件,减少异常概率。
  • 队列化处理:将待处理的文件路径加入线程安全队列,由单独的后台线程处理文件操作,避免EventLog的线程上下文问题,同时防止事件重复触发导致的重复处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 05:03:19