I'm developing Windows Service that is using FileSystemWatcher to trigger on creation of a new folder. OnCreated it reads a file insp_board.txt that is located in that new folder, logs data into log.txt and waits for another folder creation trigger. When I run code in console app it works fine, but when I run it as a Windows Service it executes OnCreated function properly but on next OnCreated execution it throws IO Exception: Could not access file because it is being used by another process.
Here is the service starting class:
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 class:
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 function:
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;
}
When running console type application the same error occurred bit I've added watcherEnableRaisingEvents = false: function at the beginning of OnCreated and watcherEnableRaisingEvents = true at the end and it worked fine. Even though I've implemented such solution in Windows Service app it is still causing the same problems. I've also tried to dispose watcher but the result was exactly the same. I've added function checking if the file is existing, and it was existing every time.
It looks like your event handler is being called twice (concurrently) because while it is processing one request, another filesystem event occurs. One variant is to use some synchronization: lock some object while you are handling an event. But this is creates a bottleneck. Other options depend on the exact task - you can move away the file for processing.
But there is more to it - I saw situations where a file was not closed in time after high-level functions like File.ReadAllLines. You may want to go for a low-level FileStream and read file bytes using it, then explicitly calling .Close and .Dispose of the object.