FileSystemWatcher cannot access files because used in other process - c#

I'm writing a FileSystemWatcher which is to copy images from folder A to folder B, whenever an image is uploaded to folder A. I'm trying to use this as a windows service on the server PC but I'm having some issues where my files are locked when they are to be copied. I think I've found the root to my issue, but I'm not having any luck solving it. So, when I run my windows service it always ends unexpectedly at either the first or the second picture upload. The error message I'm getting says this: The process cannot access the file 'filepath' because it is being used by another process.
Relevant parts of my code:
public void WatchForChanges()
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = Program.SourceFolder;
watcher.Created += new FileSystemEventHandler(OnImageAdded);
watcher.EnableRaisingEvents = true;
watcher.IncludeSubdirectories = true;
}
public void OnImageAdded(object source, FileSystemEventArgs e)
{
FileInfo file = new FileInfo(e.FullPath);
ImageHandler handler = new ImageHandler();
if (handler.IsImage(file))
{
handler.CopyImage(file);
}
}
and, my CopyImage method, which includes one of my proposed solutions to this problem, utilizing a while loop that catches the error and retries the copying of the image:
public void CopyImage(FileSystemInfo file)
{
// code that sets folder paths
// code that sets folder paths
bool retry = true;
if (!Directory.Exists(targetFolderPath))
{
Directory.CreateDirectory(targetFolderPath);
}
while (retry)
{
try
{
File.Copy(file.FullName, targetPath, true);
retry = false;
}
catch (Exception e)
{
Thread.Sleep(2000);
}
}
}
but this CopyImage solution just keeps on copying the same file, which is not very ideal in my case. I wish it was enough but sadly I've got a queue of images waiting.

The image file is probably being created by another application that uses an exclusive access lock on both reading and writing external processes (for more informations, read this, especially the paragraph related to Microsoft Windows). You have to either:
stop/kill the process which is using the file;
wait until the file isn't being used anymore.
Since the other process is probably writing the file in the moment you try to copy it with your application, the first option is by no means recommendable. It could also be an anti-virus checking the new file, and even in this case the first option would not be recommendable.
You could try to integrate the following code into your CopyImage method so that your application will wait until the file will be no longer in use before proceeding:
private Boolean WaitForFile(String filePath)
{
Int32 tries = 0;
while (true)
{
++tries;
Boolean wait = false;
FileStream stream = null;
try
{
stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
break;
}
catch (Exception ex)
{
Logger.LogWarning("CopyImage({0}) failed to get an exclusive lock: {1}", filePath, ex.ToString());
if (tries > 10)
{
Logger.LogWarning("CopyImage({0}) skipped the file after 10 tries.", filePath);
return false;
}
wait = true;
}
finally
{
if (stream != null)
stream.Close();
}
if (wait)
Thread.Sleep(250);
}
Logger.LogWarning("CopyImage({0}) got an exclusive lock after {1} tries.", filePath, tries);
return true;
}

While it seems straightforward, it's really unsatisfactorily complex.
The problem is, the application that's writing the file isn't done with it when you get the notification...and so you have a concurrency problem. There is no great way to know when the file closes. Well..one way is to subscribe to journal events - which is what FileSystemWatcher does - but this is fairly involved and requires a lot of moving parts. Going this route, you can be notified when the file closes. If you're interested, see https://msdn.microsoft.com/en-us/library/windows/desktop/aa363798(v=vs.85).aspx.
I'd divide the work into two parts. I think I'd start a ThreadPool thread to do the work, and have it read it's work from a list that the FileSystemWatcher's event handler writes to. That way, the event handler returns quickly. The ThreadPool thread would go through it's list, attempting to get an exclusive lock (similar to Tommaso's code) on the file. If it can't, it just moves on to the next file. Every time it successfully copies, it removes that file from the list.
You need to be concerned about thread safety...so you'd want to make a static object to coordinate writes to the list. Both the event handler and the ThreadPool thread would hold the lock while writing.
Here's a scaffold of the whole approach:
internal sealed class Copier: IDisposable
{
static object sync = new object();
bool quit;
FileSystemWatcher watcher;
List<string> work;
internal Copier( string pathToWatch )
{
work = new List<string>();
watcher = new FileSystemWatcher();
watcher.Path = pathToWatch;
watcher.Create += QueueWork;
ThreadPool.QueueUserWorkItem( TryCopy );
}
void Dispose()
{
lock( sync ) quit = true;
}
void QueueWork( object source, FileSystemEventArgs args )
{
lock ( sync )
{
work.Add( args.FullPath );
}
}
void TryCopy( object args )
{
List<string> localWork;
while( true )
{
lock ( sync )
{
if ( quit ) return; //--> we've been disposed
localWork = new List<string>( work );
}
foreach( var fileName in localWork )
{
var locked = true;
try
{
using
( var throwAway = new FileStream
( fileName,
FileMode.Open,
FileAccess.Read,
FileShare.None
)
); //--> no-op - will throw if we can't get exclusive read
locked = false;
}
catch { }
if (!locked )
{
File.Copy( fileName, ... );
lock( sync ) work.Remove( fileName );
}
}
}
}
}
Not tested - wrote it right here in the answer...but it, or something like it will cover the bases.

Related

How to delete a file which is used in another thread or process in c#

Here, i have a some format file which contains 2 folders and some xml files in that folders.While opening that file i extract that file in temporary location like appdata.
Then read each and every file and rendered some UI elements based upon the saved file texts.While opening i will show the waiting indicator box and that loaded process are doing in background with dispatcher.
If i want to abort that operation click abort and not needed to perform any operations(Render UI elements).Here i will delete that temporary location it will show "the process cannot access this file because it is used by another process" exception.May i know how to delete that file or which process is used that file?
Here is my code:
I made an edit, Using CancellationToken for abort all methods.
var backGroundWorker = new CancelSupportedBackgroundWorker { WorkerSupportsCancellation = true };
CancellationTokenSource source = new CancellationTokenSource();
AlertBox = new AlertBox { IsBusy = true };
AlertBox.WaitingText = Loading;
AlertBox.WaitingHeaderText = Loading Indicator;
AlertBox.AbortButton.Click += (obj, arg) =>
{
App.Current.Dispatcher.Invoke(new Action(() =>
{
string tempDir = C:\Users\Lenovo\AppData + FileAndDirectoryConstants.TempDirFolder;
if (Directory.Exists(tempDir))
{
Directory.Delete(tempDir, true);
}
}));
GC.Collect();
source.Cancel();
GC.WaitForPendingFinalizers();
backGroundWorker.CancelAsync();
backGroundWorker.Abort();
backGroundWorker.Dispose();
}
backGroundWorker.DoWork += (obj, arg) =>
{
try
{
RenderUiElements(fileName,(CancellationToken)arg.Argument);
}
catch (ThreadAbortException)
{
Dispatcher.Invoke(() =>
{
AlertBox.IsBusy = false;
}, System.Windows.Threading.DispatcherPriority.Background);
arg.Cancel = true;
}
};
backGroundWorker.RunWorkerCompleted += (obj, arg) =>
{
AlertBox.isBusy = false;
}
backGroundWorker.RunWorkerAsync(source.Token);
private void RenderUiElements(string fileName,CancellationToken token)
{
try
{
if(token != default(CancellationToken))
if(token.isCancellationRequested)
return;
App.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
delegate ()
{
using (fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
{
LoadReport(fileStream, fileName,token);
}
}));
}
catch (ThreadAbortException)
{
return;
}
finally
{
if (fileStream != null)
{
fileStream.Close();
fileStream.Dispose();
}
}
}
There are only 2 ways you can delete a file in use by another process
force all file handles currently open on a file to be closed, then delete the file
P/Invoke the Windows MoveFileEx function, and use the MOVEFILE_DELAY_UNTIL_REBOOT flag, with a NULL destination name. This will delete the file when you reboot.
I'd recommend you perform the latter, as doing the former is nasty and could cause serious problems for other running apps. For more info on the latter, see http://www.pinvoke.net/default.aspx/kernel32.movefileex
Finally, I found that solution,
Here the Filestream object is doesn't properly disposed which is disposed only after LoadReport method completely. If i click abort while doing LoadReport the filestream doesn't disposed.So that error occurred.
So clear the Filestream object in LoadReport() method.
private void LoadReport(stream filestream,CancellationToken token)
{
filestream.Close();
filestream.Dispose();
}

Tell if a file is written to a networkshare completely [duplicate]

When a file is created (FileSystemWatcher_Created) in one directory I copy it to another. But When I create a big (>10MB) file it fails to copy the file, because it starts copying already, when the file is not yet finished creating...
This causes Cannot copy the file, because it's used by another process to be raised. ;(
Any help?
class Program
{
static void Main(string[] args)
{
string path = #"D:\levan\FolderListenerTest\ListenedFolder";
FileSystemWatcher listener;
listener = new FileSystemWatcher(path);
listener.Created += new FileSystemEventHandler(listener_Created);
listener.EnableRaisingEvents = true;
while (Console.ReadLine() != "exit") ;
}
public static void listener_Created(object sender, FileSystemEventArgs e)
{
Console.WriteLine
(
"File Created:\n"
+ "ChangeType: " + e.ChangeType
+ "\nName: " + e.Name
+ "\nFullPath: " + e.FullPath
);
File.Copy(e.FullPath, #"D:\levan\FolderListenerTest\CopiedFilesFolder\" + e.Name);
Console.Read();
}
}
There is only workaround for the issue you are facing.
Check whether file id in process before starting the process of copy. You can call the following function until you get the False value.
1st Method, copied directly from this answer:
private bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//the file is unavailable because it is:
//still being written to
//or being processed by another thread
//or does not exist (has already been processed)
return true;
}
finally
{
if (stream != null)
stream.Close();
}
//file is not locked
return false;
}
2nd Method:
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;
private bool IsFileLocked(string file)
{
//check that problem is not in destination file
if (File.Exists(file) == true)
{
FileStream stream = null;
try
{
stream = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (Exception ex2)
{
//_log.WriteLog(ex2, "Error in checking whether file is locked " + file);
int errorCode = Marshal.GetHRForException(ex2) & ((1 << 16) - 1);
if ((ex2 is IOException) && (errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION))
{
return true;
}
}
finally
{
if (stream != null)
stream.Close();
}
}
return false;
}
From the documentation for FileSystemWatcher:
The OnCreated event is raised as soon as a file is created. If a file
is being copied or transferred into a watched directory, the
OnCreated event will be raised immediately, followed by one or more
OnChanged events.
So, if the copy fails, (catch the exception), add it to a list of files that still need to be moved, and attempt the copy during the OnChanged event. Eventually, it should work.
Something like (incomplete; catch specific exceptions, initialize variables, etc):
public static void listener_Created(object sender, FileSystemEventArgs e)
{
Console.WriteLine
(
"File Created:\n"
+ "ChangeType: " + e.ChangeType
+ "\nName: " + e.Name
+ "\nFullPath: " + e.FullPath
);
try {
File.Copy(e.FullPath, #"D:\levani\FolderListenerTest\CopiedFilesFolder\" + e.Name);
}
catch {
_waitingForClose.Add(e.FullPath);
}
Console.Read();
}
public static void listener_Changed(object sender, FileSystemEventArgs e)
{
if (_waitingForClose.Contains(e.FullPath))
{
try {
File.Copy(...);
_waitingForClose.Remove(e.FullPath);
}
catch {}
}
}
It's an old thread, but I'll add some info for other people.
I experienced a similar issue with a program that writes PDF files, sometimes they take 30 seconds to render.. which is the same period that my watcher_FileCreated class waits before copying the file.
The files were not locked.
In this case I checked the size of the PDF and then waited 2 seconds before comparing the new size, if they were unequal the thread would sleep for 30 seconds and try again.
You're actually in luck - the program writing the file locks it, so you can't open it. If it hadn't locked it, you would have copied a partial file, without having any idea there's a problem.
When you can't access a file, you can assume it's still in use (better yet - try to open it in exclusive mode, and see if someone else is currently opening it, instead of guessing from the failure of File.Copy). If the file is locked, you'll have to copy it at some other time. If it's not locked, you can copy it (there's slight potential for a race condition here).
When is that 'other time'? I don't rememeber when FileSystemWatcher sends multiple events per file - check it out, it might be enough for you to simply ignore the event and wait for another one. If not, you can always set up a time and recheck the file in 5 seconds.
Well you already give the answer yourself; you have to wait for the creation of the file to finish. One way to do this is via checking if the file is still in use. An example of this can be found here: Is there a way to check if a file is in use?
Note that you will have to modify this code for it to work in your situation. You might want to have something like (pseudocode):
public static void listener_Created()
{
while CheckFileInUse()
wait 1000 milliseconds
CopyFile()
}
Obviously you should protect yourself from an infinite while just in case the owner application never releases the lock. Also, it might be worth checking out the other events from FileSystemWatcher you can subscribe to. There might be an event which you can use to circumvent this whole problem.
When the file is writing in binary(byte by byte),create FileStream and above solutions Not working,because file is ready and wrotted in every bytes,so in this Situation you need other workaround like this:
Do this when file created or you want to start processing on file
long fileSize = 0;
currentFile = new FileInfo(path);
while (fileSize < currentFile.Length)//check size is stable or increased
{
fileSize = currentFile.Length;//get current size
System.Threading.Thread.Sleep(500);//wait a moment for processing copy
currentFile.Refresh();//refresh length value
}
//Now file is ready for any process!
So, having glanced quickly through some of these and other similar questions I went on a merry goose chase this afternoon trying to solve a problem with two separate programs using a file as a synchronization (and also file save) method. A bit of an unusual situation, but it definitely highlighted for me the problems with the 'check if the file is locked, then open it if it's not' approach.
The problem is this: the file can become locked between the time that you check it and the time you actually open the file. Its really hard to track down the sporadic Cannot copy the file, because it's used by another process error if you aren't looking for it too.
The basic resolution is to just try to open the file inside a catch block so that if its locked, you can try again. That way there is no elapsed time between the check and the opening, the OS does them at the same time.
The code here uses File.Copy, but it works just as well with any of the static methods of the File class: File.Open, File.ReadAllText, File.WriteAllText, etc.
/// <param name="timeout">how long to keep trying in milliseconds</param>
static void safeCopy(string src, string dst, int timeout)
{
while (timeout > 0)
{
try
{
File.Copy(src, dst);
//don't forget to either return from the function or break out fo the while loop
break;
}
catch (IOException)
{
//you could do the sleep in here, but its probably a good idea to exit the error handler as soon as possible
}
Thread.Sleep(100);
//if its a very long wait this will acumulate very small errors.
//For most things it's probably fine, but if you need precision over a long time span, consider
// using some sort of timer or DateTime.Now as a better alternative
timeout -= 100;
}
}
Another small note on parellelism:
This is a synchronous method, which will block its thread both while waiting and while working on the thread. This is the simplest approach, but if the file remains locked for a long time your program may become unresponsive. Parellelism is too big a topic to go into in depth here, (and the number of ways you could set up asynchronous read/write is kind of preposterous) but here is one way it could be parellelized.
public class FileEx
{
public static async void CopyWaitAsync(string src, string dst, int timeout, Action doWhenDone)
{
while (timeout > 0)
{
try
{
File.Copy(src, dst);
doWhenDone();
break;
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
}
public static async Task<string> ReadAllTextWaitAsync(string filePath, int timeout)
{
while (timeout > 0)
{
try {
return File.ReadAllText(filePath);
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
return "";
}
public static async void WriteAllTextWaitAsync(string filePath, string contents, int timeout)
{
while (timeout > 0)
{
try
{
File.WriteAllText(filePath, contents);
return;
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
}
}
And here is how it could be used:
public static void Main()
{
test_FileEx();
Console.WriteLine("Me First!");
}
public static async void test_FileEx()
{
await Task.Delay(1);
//you can do this, but it gives a compiler warning because it can potentially return immediately without finishing the copy
//As a side note, if the file is not locked this will not return until the copy operation completes. Async functions run synchronously
//until the first 'await'. See the documentation for async: https://msdn.microsoft.com/en-us/library/hh156513.aspx
CopyWaitAsync("file1.txt", "file1.bat", 1000);
//this is the normal way of using this kind of async function. Execution of the following lines will always occur AFTER the copy finishes
await CopyWaitAsync("file1.txt", "file1.readme", 1000);
Console.WriteLine("file1.txt copied to file1.readme");
//The following line doesn't cause a compiler error, but it doesn't make any sense either.
ReadAllTextWaitAsync("file1.readme", 1000);
//To get the return value of the function, you have to use this function with the await keyword
string text = await ReadAllTextWaitAsync("file1.readme", 1000);
Console.WriteLine("file1.readme says: " + text);
}
//Output:
//Me First!
//file1.txt copied to file1.readme
//file1.readme says: Text to be duplicated!
You can use the following code to check if the file can be opened with exclusive access (that is, it is not opened by another application). If the file isn't closed, you could wait a few moments and check again until the file is closed and you can safely copy it.
You should still check if File.Copy fails, because another application may open the file between the moment you check the file and the moment you copy it.
public static bool IsFileClosed(string filename)
{
try
{
using (var inputStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
{
return true;
}
}
catch (IOException)
{
return false;
}
}
I would like to add an answer here, because this worked for me. I used time delays, while loops, everything I could think of.
I had the Windows Explorer window of the output folder open. I closed it, and everything worked like a charm.
I hope this helps someone.

Multithreading design-pattern

While keeping in mind that:
I am using a blocking queue that waits for ever until something is added to it
I might get a FileSystemWatcher event twice
The updated code:
{
FileProcessingManager processingManager = new FileProcessingManager();
processingManager.RegisterProcessor(new ExcelFileProcessor());
processingManager.RegisterProcessor(new PdfFileProcessor());
processingManager.Completed += new ProcessingCompletedHandler(ProcessingCompletedHandler);
processingManager.Completed += new ProcessingCompletedHandler(LogFileStatus);
while (true)
{
try
{
var jobData = (JobData)fileMonitor.FileQueue.Dequeue();
if (jobData == null)
break;
_pool.WaitOne();
Application.Log(String.Format("{0}:{1}", DateTime.Now.ToString(CultureInfo.InvariantCulture), "Thread launched"));
Task.Factory.StartNew(() => processingManager.Process(jobData));
}
catch (Exception e)
{
Application.Log(String.Format("{0}:{1}", DateTime.Now.ToString(CultureInfo.InvariantCulture), e.Message));
}
}
}
What are are you suggestions on making the code multi-threaded while taking into consideration the possibility that two identical string paths may be added into the blocking queue? I have left the possibility that this might happen and in this case.. the file would be processed twice, the thing is that sometimes I get it twice, sometimes not, it is really awkward, if you have suggestions on this, please tell.
The null checking is for exiting the loop, I intentionally add a null from outside the threaded loop to determine it to stop.
For multi-threading this... I would probably add a "Completed" event to your FileProcessingManager and register for it. One argument of that event will be the "bool" return value you currently have. Then in that event handler, I would do the checking of the bool and re-queueing of the file. Note that you will have to keep a reference to the FileMonitorManager. So, I would have this ThreadProc method be in a class where you keep the FileMonitorManager and FileProcessingManager instances in a property.
To deduplicate, in ThreadProc, I would create a List outside of the while loop. Then inside the while loop, before you process a file, lock that list, check to see if the string is already in there, if not, add the string to the list and process the file, if it is, then skip processing.
Obviously, this is based on little information surrounding your method but my 2 cents anyway.
Rough code, from Notepad:
private static FileMonitorManager fileMon = null;
private static FileProcessingManager processingManager = new FileProcessingManager();
private static void ThreadProc(object param)
{
processingManager.RegisterProcessor(new ExcelFileProcessor());
processingManager.RegisterProcessor(new PdfFileProcessor());
processingManager.Completed += ProcessingCompletedHandler;
var procList = new List<string>();
while (true)
{
try
{
var path = (string)fileMon.FileQueue.Dequeue();
if (path == null)
break;
bool processThis = false;
lock(procList)
{
if(!procList.Contains(path))
{
processThis = true;
procList.Add(path);
}
}
if(processThis)
{
Thread t = new Thread (new ParameterizedThreadStart(processingManager.Process));
t.Start (path);
}
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);
}
}
}
private static void ProcessingCompletedHandler(bool status, string path)
{
if (!status)
{
fileMon.FileQueue.Enqueue(path);
Console.WriteLine("\n\nError on file: " + path);
}
else
Console.WriteLine("\n\nSucces on file: " + path);
}

FileSystemWatcher - is File ready to use

When a file is being copied to the file watcher folder, how can I identify whether the file is completely copied and ready to use? Because I am getting multiple events during file copy. (The file is copied via another program using File.Copy.)
When I ran into this problem, the best solution I came up with was to continually try to get an exclusive lock on the file; while the file is being written, the locking attempt will fail, essentially the method in this answer. Once the file isn't being written to any more, the lock will succeed.
Unfortunately, the only way to do that is to wrap a try/catch around opening the file, which makes me cringe - having to use try/catch is always painful. There just doesn't seem to be any way around that, though, so it's what I ended up using.
Modifying the code in that answer does the trick, so I ended up using something like this:
private void WaitForFile(FileInfo file)
{
FileStream stream = null;
bool FileReady = false;
while(!FileReady)
{
try
{
using(stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
FileReady = true;
}
}
catch (IOException)
{
//File isn't ready yet, so we need to keep on waiting until it is.
}
//We'll want to wait a bit between polls, if the file isn't ready.
if(!FileReady) Thread.Sleep(1000);
}
}
Here is a method that will retry file access up to X number of times, with a Sleep between tries. If it never gets access, the application moves on:
private static bool GetIdleFile(string path)
{
var fileIdle = false;
const int MaximumAttemptsAllowed = 30;
var attemptsMade = 0;
while (!fileIdle && attemptsMade <= MaximumAttemptsAllowed)
{
try
{
using (File.Open(path, FileMode.Open, FileAccess.ReadWrite))
{
fileIdle = true;
}
}
catch
{
attemptsMade++;
Thread.Sleep(100);
}
}
return fileIdle;
}
It can be used like this:
private void WatcherOnCreated(object sender, FileSystemEventArgs e)
{
if (GetIdleFile(e.FullPath))
{
// Do something like...
foreach (var line in File.ReadAllLines(e.FullPath))
{
// Do more...
}
}
}
I had this problem when writing a file. I got events before the file was fully written and closed.
The solution is to use a temporary filename and rename the file once finished. Then watch for the file rename event instead of file creation or change event.
Note: this problem is not solvable in generic case. Without prior knowledge about file usage you can't know if other program(s) finished operation with the file.
In your particular case you should be able to figure out what operations File.Copy consist of.
Most likely destination file is locked during whole operation. In this case you should be able to simply try to open file and handle "sharing mode violation" exception.
You can also wait for some time... - very unreliable option, but if you know size range of files you may be able to have reasonable delay to let Copy to finish.
You can also "invent" some sort of transaction system - i.e. create another file like "destination_file_name.COPYLOCK" which program that copies file would create before copying "destination_file_name" and delete afterward.
private Stream ReadWhenAvailable(FileInfo finfo, TimeSpan? ts = null) => Task.Run(() =>
{
ts = ts == null ? new TimeSpan(long.MaxValue) : ts;
var start = DateTime.Now;
while (DateTime.Now - start < ts)
{
Thread.Sleep(200);
try
{
return new FileStream(finfo.FullName, FileMode.Open);
}
catch { }
}
return null;
})
.Result;
...of course, you can modify aspects of this to suit your needs.
One possible solution (It worked in my case) is to use the Change event. You can log in the create event the name of the file just created and then catch the change event and verify if the file was just created. When I manipulated the file in the change event it didn't throw me the error "File is in use"
If you are doing some sort of inter-process communication, as I do, you may want to consider this solution:
App A writes the file you are interested in, eg "Data.csv"
When done, app A writes a 2nd file, eg. "Data.confirmed"
In your C# app B make the FileWatcher listen to "*.confirmed" files. When you get this event you can safely read "Data.csv", as it is already completed by app A.
(Edit: inspired by commets) Delete the *.confirmed filed with app B when done processing the "Data.csv" file.
I have solved this issue with two features:
Implement the MemoryCache pattern seen in this question: A robust solution for FileSystemWatcher firing events multiple times
Implement a try\catch loop with a timeout for access
You need to collect average copy times in your environment and set the memory cache timeout to be at least as long as the shortest lock time on a new file. This eliminates duplicates in your processing directive and allows some time for the copy to finish. You will have much better success on first try, which means less time spent in the try\catch loop.
Here is an example of the try\catch loop:
public static IEnumerable<string> GetFileLines(string theFile)
{
DateTime startTime = DateTime.Now;
TimeSpan timeOut = TimeSpan.FromSeconds(TimeoutSeconds);
TimeSpan timePassed;
do
{
try
{
return File.ReadLines(theFile);
}
catch (FileNotFoundException ex)
{
EventLog.WriteEntry(ProgramName, "File not found: " + theFile, EventLogEntryType.Warning, ex.HResult);
return null;
}
catch (PathTooLongException ex)
{
EventLog.WriteEntry(ProgramName, "Path too long: " + theFile, EventLogEntryType.Warning, ex.HResult);
return null;
}
catch (DirectoryNotFoundException ex)
{
EventLog.WriteEntry(ProgramName, "Directory not found: " + theFile, EventLogEntryType.Warning, ex.HResult);
return null;
}
catch (Exception ex)
{
// We swallow all other exceptions here so we can try again
EventLog.WriteEntry(ProgramName, ex.Message, EventLogEntryType.Warning, ex.HResult);
}
Task.Delay(777).Wait();
timePassed = DateTime.Now.Subtract(startTime);
}
while (timePassed < timeOut);
EventLog.WriteEntry(ProgramName, "Timeout after waiting " + timePassed.ToString() + " seconds to read " + theFile, EventLogEntryType.Warning, 258);
return null;
}
Where TimeoutSeconds is a setting that you can put wherever you hold your settings. This can be tuned for your environment.

Locking file in .NET

I need to modify a text file from multiple .NET processes, nothing I've tried works reliably. I have a C# GUI app which starts multiple processes to do some number crunching. Those need to append lines to the same text file every few milliseconds. The master process monitors the size of the file and once it reaches some threshold uploads it and deletes it.
The way these are currently coded, the processes that append text create the file if it doesn't exist, but that would be easy to change.
How can I implement this?
This method will repeatedly attempt to open the file until it can write to it, timing out after 10ms.
private static readonly TimeSpan timeoutPeriod = new TimeSpan(100000); // 10ms
private const string filename = "Output.txt";
public void WriteData(string data)
{
StreamWriter writer = null;
DateTime timeout = DateTime.Now + timeoutPeriod;
try
{
do
{
try
{
// Try to open the file.
writer = new StreamWriter(filename);
}
catch (IOException)
{
// If this is taking too long, throw an exception.
if (DateTime.Now >= timeout) throw new TimeoutException();
// Let other threads run so one of them can unlock the file.
Thread.Sleep(0);
}
}
while (writer == null);
writer.WriteLine(data);
}
finally
{
if (writer != null) writer.Dispose();
}
}

Categories

Resources