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

