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

Windows服务中FileSystemWatcher触发文件读取时出现文件被占用IO异常

问题:Windows服务中FileSystemWatcher二次触发IO异常

我开发了一个Windows服务,使用FileSystemWatcher监听新文件夹的创建事件。触发OnCreated事件时,服务会读取新文件夹中的insp_board.txt文件,将数据记录到log.txt中,然后等待下一次文件夹创建触发。

控制台应用中运行代码时一切正常,但作为Windows服务运行时,首次OnCreated函数执行正常,第二次触发OnCreated时会抛出IO异常:"Could not access file because it is being used by another process"。


相关代码

服务启动类

public partial class SPIHydraImport : ServiceBase
{
    private SourceWatcher sourceWatcher;

    public SPIHydraImport()
    {
        InitializeComponent();
        sourceWatcher = new SourceWatcher();
        var eventLogger = new EventLogger("HydraSPIImport", "Application");
    }

    protected override void OnStart(string[] args)
    {
        try
        {
            sourceWatcher.StartWatching();
        }
        catch (Exception ex)
        {
            EventLog.WriteEntry("An error occurred: " + ex.Message, EventLogEntryType.Error);
            throw;
        }
    }

    protected override void OnStop()
    {
        sourceWatcher.StopWatching();
    }
}

Watcher类

public class SourceWatcher
{
    private FileSystemWatcher watcher;
    private Logger logger = new Logger();

    private string sourcePath = ConfigurationManager.AppSettings["SourcePath"];
    public SourceWatcher()
    {
        watcher = new FileSystemWatcher();
        watcher.Path = sourcePath;
        watcher.IncludeSubdirectories = true;
        watcher.Filter = "insp_board.txt";
        watcher.Created += new FileSystemEventHandler(OnCreated);
        watcher.EnableRaisingEvents = true;
    }

    public void StartWatching()
    {
        watcher.EnableRaisingEvents = true;
        this.logger.LogStandard($"Start: watcher");
    }

    private void OnCreated(object source, FileSystemEventArgs e)
    {
        StopWatching();
        BoardData boardData = new BoardData();
        string directoryPath = e.FullPath;

        try
        {
            this.logger.LogStandard($"{directoryPath}");
            boardData = boardData.ParseFile(directoryPath);
            this.logger.LogStandard($"BoardID: {boardData.boardId} , Operation: {boardData.operationId} , PCB ID: {boardData.pcbId} , Inspection time: {boardData.inspectionTime} , Tack time: {boardData.tackTime} , Is pass: {boardData.isPass}");
        }
        catch (Exception exc)
        {
            this.logger.LogError(exc.ToString());
            throw;
        }
        finally
        {
            StartWatching();
        }
    }

    public void StopWatching()
    {
        watcher.EnableRaisingEvents = false;
        this.logger.LogStandard("Stop: watcher");
    }
}

ParseFile函数

public BoardData ParseFile(string inspBoardPath)
{
    var boardData = new BoardData();
    var lines = File.ReadAllLines(inspBoardPath).Skip(2);

    foreach (var line in lines)
    {
        var parts = line.Split(',');

        boardData.boardId = parts[1];
        boardData.operationId = "0" + parts[3];
        boardData.pcbId = parts[4];
        boardData.inspectionTime = DateTime.ParseExact(parts[5], "yyyyMMddHHmmss", CultureInfo.InvariantCulture).ToString("yyyy-MM-dd HH:mm:ss");
        boardData.tackTime = double.Parse(parts[6], CultureInfo.InvariantCulture);
        if (parts[7].Equals("3"))
        {
            boardData.isPass = 1;
        } else
        {
            boardData.isPass = 0;
        }
    }

    return boardData;
}

我在控制台应用中曾遇到相同错误,通过在OnCreated开头禁用事件、结尾启用事件解决了问题,但Windows服务中同样实现该方案后问题依旧。尝试过释放Watcher,结果相同;添加文件存在检查,每次都能确认文件存在。


解决方案

1. 处理文件未完全写入的问题

FileSystemWatcher的Created事件会在文件开始创建时触发,此时文件可能仍被写入进程占用。服务环境下文件写入的锁机制或延迟与控制台不同,需等待文件可用后再读取:

添加文件可用性检查方法:

private bool IsFileReady(string filePath)
{
    try
    {
        // 尝试以独占方式打开文件,确认无其他进程占用
        using (var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
        {
            return true;
        }
    }
    catch (IOException)
    {
        return false;
    }
}

修改OnCreated中的读取逻辑,增加重试等待:

private void OnCreated(object source, FileSystemEventArgs e)
{
    StopWatching();
    BoardData boardData = new BoardData();
    string filePath = e.FullPath;

    try
    {
        this.logger.LogStandard($"{filePath}");
        
        // 等待文件可用,最多重试10次,每次间隔500ms
        int retryCount = 0;
        while (!IsFileReady(filePath) && retryCount < 10)
        {
            System.Threading.Thread.Sleep(500);
            retryCount++;
        }

        if (retryCount >= 10)
        {
            this.logger.LogError($"文件 {filePath} 长时间被占用,放弃读取");
            return;
        }

        boardData = boardData.ParseFile(filePath);
        this.logger.LogStandard($"BoardID: {boardData.boardId} , Operation: {boardData.operationId} , PCB ID: {boardData.pcbId} , Inspection time: {boardData.inspectionTime} , Tack time: {boardData.tackTime} , Is pass: {boardData.isPass}");
    }
    catch (Exception exc)
    {
        this.logger.LogError(exc.ToString());
        throw;
    }
    finally
    {
        StartWatching();
    }
}

2. 优化FileSystemWatcher配置

  • 移除构造函数中直接启用EnableRaisingEvents的代码,统一在StartWatching中控制,避免初始化时意外触发事件:
    public SourceWatcher()
    {
        watcher = new FileSystemWatcher();
        watcher.Path = sourcePath;
        watcher.IncludeSubdirectories = true;
        watcher.Filter = "insp_board.txt";
        watcher.Created += new FileSystemEventHandler(OnCreated);
        // 注释掉这一行
        // watcher.EnableRaisingEvents = true;
    }
    
  • 添加NotifyFilter,只关注文件写入完成的相关事件,减少误触发:
    watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size;
    

3. 检查Logger的文件占用

确认Logger类在写入log.txt时是否正确释放了文件句柄。如果Logger使用了持续打开的文件流,会导致文件被长期占用,需修改Logger实现,确保每次写入后关闭流,或使用File.AppendAllText这类自动释放资源的方法。

4. 确认服务运行权限

确保Windows服务的运行账户对监控目录和文件有读取和写入权限。可以尝试将服务运行账户改为本地系统账户(Local System)测试,排除权限导致的异常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 19:05:35