Filewatcher is consuming most of PC resource - c#

I am developing an application which tracks directory related changes on available drives of PC and save those changes in the sqlite DB. Everything in the below code is working fine as expected. But when I tested it by copying approx 100 directory and their nested directories together, it freeze the application and also freeze copy process of Windows OS.
I am not getting the way how to manage it because this code for bulk file copying is consuming most of the PC resource. Is there any why I can optimize it? rather setting "IncludeSubDirectories = False"?
class DirWatcher
{
private FileSystemWatcher dirWatcher = null;
public void StartCapture()
{
string[] drives = Environment.GetLogicalDrives();
foreach (string drive in drives)
{
DriveInfo driveInfo = new DriveInfo(drive);
if (driveInfo.DriveType == DriveType.Fixed)
{
//Director Watcher
dirWatcher = new FileSystemWatcher(drive);
dirWatcher.NotifyFilter = NotifyFilters.DirectoryName;
dirWatcher.Created += dirWatcher_Created;
dirWatcher.Deleted += dirWatcher_Created;
dirWatcher.Renamed += dirWatcher_Renamed;
dirWatcher.IncludeSubdirectories = true;
dirWatcher.EnableRaisingEvents = true;
}
}
}
void dirWatcher_Renamed(object sender, RenamedEventArgs e)
{
try
{
Task.Factory.StartNew(() =>
{
saveToDB("Folder",
e.ChangeType.ToString(),
e.FullPath,
Utility.UnixDTstamp(DateTime.Now).ToString(),
Environment.UserName);
});
}
finally
{
dirWatcher.Renamed -= dirWatcher_Renamed;
}
}
void dirWatcher_Created(object sender, FileSystemEventArgs e)
{
try
{
Task.Factory.StartNew(() =>
{
saveToDB("Folder",
e.ChangeType.ToString(),
e.FullPath,
Utility.UnixDTstamp(DateTime.Now).ToString(),
Environment.UserName);
});
}
finally
{
dirWatcher.Created -= dirWatcher_Created;
}
}
public void StopCapture()
{
dirWatcher.IncludeSubdirectories = false;
dirWatcher.EnableRaisingEvents = false;
dirWatcher.Dispose();
}
public void saveToDB(string DirOrFile, string action, string path, string time, string userName)
{
//SavetoDB code will be here.
}
}
}

One thing that may be slowing down ( or even defeating) your program are attached drives on the network. I don't believe that you can successfully set a Filewatcher on these, especially if the drivers are LINUX based. And even if you could, it would be glacial in performance
I have also found Filewatcher to miss events when watching folder hierarchies. So in general, I'd say that you need to find or write a replacement for this functionality that is so flawed it should be deprecated. Perhaps a hook that lets you watch the windows message loop?

Related

How to wait until a file is successfully copied from a network drive before reading from it?

I am currently facing a problem with an application which consists of multiple components.
One component of the application periodically checks for new files on a network drive and copies them into a local folder. Another component of the application uses a FileSystemWatcher to watch for any new files in the local folder. If a new file is copied, the Created event of the FileSystemWatcher gets called and the application will then read the file contents and import the file into a database.
To prevent the application from trying to read the file before it is fully copied into the local folder, it calls the following function periodically until it returns false:
private bool isFileLocked(string filePath)
{
try
{
if (!File.Exists(filePath))
{
return false;
}
using (FileStream fs = File.OpenRead(filePath))
{
}
return false;
}
catch (IOException)
{
return true;
}
}
Unfortunately this does not seem to work in all cases. Sometimes, I noticed that the file is being read before it is completely written into the local folder. When this happens, the component which tries to copy the file gets the following error:
System.IO.IOException: The process cannot access the file '...' because it is being used by another process.
The component which copies the file is written in PowerShell and uses the following Cmdlet for copying:
Copy-Item $currentfile.FullName -Destination "$destfolder" –Force -ErrorAction Stop
The component which uses the FileSystemWatcher and imports the file is a C# based windows service.
How can I prevent it from reading the file before it is fully copied into the local folder?
If you don't worry about little delay - it may solve your trouble:
static void Main(string[] args)
{
FileSystemWatcher fsw = new FileSystemWatcher("SomePathToFolder");
fsw.EnableRaisingEvents = true;
fsw.Created += async (s, a) =>
{
while (FileIsLocked(a.FullPath))
{
Console.WriteLine($"File {a.Name} is locked!");
await Task.Delay(TimeSpan.FromSeconds(5)); // 5 seconds delay between checks
}
Console.WriteLine($"File {a.Name} available!");
// You can put here another delay to be 102% sure that file is free,
// but I suppose this is too much.
using (FileStream fs = File.OpenRead(a.FullPath))
{
Console.WriteLine($"File {a.Name} opened for reading.");
// Do what you need
await Task.Run(() => ImportFileToDatabase(fs));
}
Console.WriteLine($"File {a.Name} closed.");
};
Console.ReadKey();
}
static bool FileIsLocked(string filePath)
{
if (!File.Exists(filePath))
return false;
try
{
using (FileStream fs = File.OpenRead(filePath)) { }
return false;
}
catch { }
return true;
}
Some solutions are suggested here. I've had a similar problem using FileSystemWatcher. This is what I use (simplified):
async Task<FileStream> OpenWaitAsync(string path, TimeSpan interval, CancellationToken cancellationToken = default)
{
const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
while (true)
{
try
{
return File.OpenRead(path);
}
catch (IOException ioe) when (ioe.HResult == ERROR_SHARING_VIOLATION)
{
await Task.Delay(interval, cancellationToken);
}
}
}

Monitor folder and copy new files once their creation is complete

I built a console app that monitors a set of folders on a Windows 2019 Server and copies any newly-created .txt files to another folder, using the same file name. So far it's working for the basic functionality. Now I have to handle the fact that most of the time, these files are large and take several minutes to complete creation. I have gone through several SO posts and pieced together the following code trying to accomplish this:
using System;
using System.IO;
namespace Folderwatch
{
class Program
{
static void Main(string[] args)
{
string sourcePath = #"C:\Users\me\Documents\SomeFolder";
FileSystemWatcher watcher = new FileSystemWatcher(sourcePath);
watcher.EnableRaisingEvents = true;
watcher.IncludeSubdirectories = true;
watcher.Filter = "*.txt";
// Add event handlers.
watcher.Created += new FileSystemEventHandler(OnCreated);
}
// Define the event handlers.
private static void OnCreated(object source, FileSystemEventArgs e)
{
// Specify what is done when a file is created.
FileInfo file = new FileInfo(e.FullPath);
string wctPath = e.FullPath;
string wctName = e.Name;
string createdFile = Path.GetFileName(wctName);
string destPath = #"C:\Users\SomeOtherFolder";
string sourceFile = wctPath;
string destFile = Path.Combine(destPath, createdFile);
WaitForFile(file);
File.Copy(sourceFile, destFile, true);
}
public static bool IsFileLocked(FileInfo file)
{
try
{
using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
{
stream.Close();
}
}
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;
}
//file is not locked
return false;
}
public static void WaitForFile(FileInfo filename)
{
//This will lock the execution until the file is ready
//TODO: Add some logic to make it async and cancelable
while (!IsFileLocked(filename)) { }
}
}
}
What I'm attempting to do in the OnCreated method is to check and wait until the file is done being created, and then copy the file to another destination. I don't seem to know what I'm doing with the WaitForFile(file) line - if I comment out that line and the file creation is instant, the file copies as intended. If I use the WaitForFile line, nothing ever happens. I took the IsFileLocked and WaitForFile methods from other posts on SO, but I'm clearly not implementing them correctly.
I've noted this Powershell version Copy File On Creation (once complete) and I'm not sure if the answer here could be pointing me in the right direction b/c I'm even less versed in PS than I am in C#.
EDIT #1: I should have tested for longer before accepting the answer - I think we're close but after about a minute of the program running, I got the following error before the program crashed:
Unhandled exception. System.IO.IOException: The process cannot access
the file
'C:\Users\me\Dropbox\test1.log'
because it is being used by another process. at
System.IO.FileSystem.CopyFile(String sourceFullPath, String
destFullPath, Boolean overwrite) at
Folderwatch.Program.OnCreated(Object source, FileSystemEventArgs e) in
C:\Users\me\OneDrive -
Development\Source\repos\FolderWatchCG\FolderWatchCG\Program.cs:line
61 at System.Threading.Tasks.Task.<>c.b__139_1(Object
state) at
System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch() at
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
Any advice on this would be appreciated. As I further analyze the files in these folders, some of them are log files getting written in realtime, so it could be that the file is being written to for hours before it's actually completed. I am wondering if somehow one of the NotifyFilter comes into play here?
There's a bug in the WaitForFile() method, that is, it currently waits while the file is not locked (not the other way around). In addition to that, you need a way to confirm that the file actually exists. A simple way to achieve that would be to change the WaitForFile() method into something like this:
public static bool WaitForFile(FileInfo file)
{
while (IsFileLocked(file))
{
// The file is inaccessible. Let's check if it exists.
if (!file.Exists) return false;
}
// The file is accessible now.
return true;
}
This will keep waiting as long as the file exists and is inaccessible.
Then, you can use it as follows:
bool fileAvailable = WaitForFile(file);
if (fileAvailable)
{
File.Copy(sourceFile, destFile, true);
}
The problem with this approach though is that the while loop keeps the thread busy, which a) consumes a considerable amount of the CPU resources, and b) prevents the program from processing other files until it finishes waiting for that one file. So, it's probably better to use an asynchronous wait between each check.
Change the WaitForFile method to:
public static async Task<bool> WaitForFile(FileInfo file)
{
while (IsFileLocked(file))
{
// The file is inaccessible. Let's check if it exists.
if (!file.Exists) return false;
await Task.Delay(100);
}
// The file is accessible now.
return true;
}
Then, await it inside OnCreated like this:
private async static void OnCreated(object source, FileSystemEventArgs e)
{
// ...
bool fileAvailable = await WaitForFile(file);
if (fileAvailable)
{
File.Copy(sourceFile, destFile, true);
}
}

Can't access the network drive while running the windows service

I'm trying to create a windows service in C# that will copy all the files from a network drive and paste it into a local drive (let's say in C drive). When I run the test case, the program runs successfully but when I install and run the windows service, the 'Access is denied' error comes in the log file.
I tried Map Network Drive (API) solution but that solution didn't work. either.
Here's the sample code that I've used to get all the files from a network drive and paste it into the local drive folder
Service1.cs
public partial class Service1 : ServiceBase
{
private Timer _timer;
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
try
{
DoWork();
}
catch (Exception e)
{
WriteErrorLog(e);
}
}
private void DoWork()
{
_timer = new Timer();
_timer.Interval = 5000;
_timer.Enabled = true;
_timer.Elapsed += _timer_Elapsed;
Update();
}
private void Update()
{
RevidAddinController.Update_AutodeskAddinFolder_With_ArchcorpUpdatedAddinFiles(Configuration.AutodeskVersion, Configuration.AutodeskRevitAddinFolderPath);
}
private void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
Update();
}
private void WriteErrorLog(Exception ex)
{
StreamWriter sw = null;
try
{
sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "\\Logfile.txt", true);
sw.WriteLine(DateTime.Now.ToString() + " ; " + ex.Source.ToString().Trim() + "; " + ex.Message.ToString().Trim());
sw.Flush();
sw.Close();
}
catch
{
}
}
protected override void OnStop()
{
}
}
RevidAddinController.cs
public static class RevidAddinController
{
public static IEnumerable<AddinStatus> Update_AutodeskAddinFolder_With_ArchcorpUpdatedAddinFiles(List<string> autoDeskVersion, string addinInstallationPath)
{
var networkDrive = ActivateNetworkDrive();
var allAutodeskVersionPath = Util.GetAllAutodeskAddinLibraryFolderPaths(autoDeskVersion, addinInstallationPath);
List<FileData> latestArchcorpAddins = new List<FileData>();
foreach (var autodeskAddinFolder in allAutodeskVersionPath)
{
var archorpAddinFiles = Util.GetAllExternalRevitAddinFilesFromArchcorpAddinFolderPath(Configuration.ArchcorpAddinFolderPath);
var autodeskAddinFiles = Util.GetAllExternalRevitAddinFilesLocationFromAutodeskAddinFolderPath(autodeskAddinFolder);
var latestAddins = Util.GetUpdatedRevitAddinFromArchcorpFolderPath(autodeskAddinFolder, archorpAddinFiles, autodeskAddinFiles)
.Where(addin => !addin.FileName.Contains(Configuration.DeleteAddinNamePrefix));
latestArchcorpAddins.AddRange(latestAddins);
}
List<AddinStatus> addinCopyStatus = new List<AddinStatus>();
foreach (var autodeskAddinPath in allAutodeskVersionPath)
{
foreach (var newArchcorpAddin in latestArchcorpAddins)
{
addinCopyStatus.Add(Util.InstallNewAddinFile(newArchcorpAddin, autodeskAddinPath));
}
}
return addinCopyStatus;
}
/// <summary>
/// Map the network drive path
/// </summary>
/// <returns></returns>
public static NetworkDrive ActivateNetworkDrive()
{
NetworkDrive oNetDrive = new aejw.Network.NetworkDrive();
try
{
oNetDrive.LocalDrive = "O:";
oNetDrive.ShareName = #"\\acdxbfs1\Organisation";
oNetDrive.Force = true;
oNetDrive.Persistent = true;
oNetDrive.MapDrive();
}
catch (Exception err)
{
throw err;
}
return oNetDrive;
}
}
The complete code can be found on the gist here. Would really appreciate if someone reviews the code and provides any feedback/solution to this problem.
Running a service under the default Local System Account, will have no concept of the share. These are set up under user accounts.
Your 2 options
Run your service under a User Account which has those shares mapped
Access your share via and ip address instead of the drive letter. However, you will need to set the file/folder permissions accordingly.
The service does run as Local System (as previously named). If you have a mapped network drive to a local drive letter, the service cannot use it (because a mapped network drive is always only mapped for the user context, not the whole computer/system). However the service can access the share by UNC \\server\share. You can view the UNC path if you only have a mapped network drive by typing 'net use' inside a command prompt.
If you run your program as a user Windows does automatically authenticate you at the remote share (if not already done by adding a mapped network drive). Therefor Local System is the computer account you need to set the access permissions of the target share to the computername eg workstation1$ (only available inside a domain cause a workgroup does not know the other computers). This has to be done for the file permissions and the share permissions because both are independent and can restrict you from the access.
As an alternative you can authenticate at the remote network share with an user and password - there is an excellent thread in stackoverflow which you can find here which does show how you can achieve this.
Naturally you can also set the service to a user/password in the services manager (services.msc - double click your service and go to the logon tab) who has access to the share. By doing this, the user will be granted the 'login as service' permission which is necessary for this.
If the network file is shared with the local system account then you need to Log In as "Local System Account",
The advantage of running your services as the "Local System account" is that the service has complete unrestricted access to local resources.
But there are some disadvantages also, so be careful to not install unauthorized services as service gets full unrestricted access. Also if the service has some bugs it may lead to performance issues.

Why sometimes when watching a file/directory for changes it's not changing the flag to true?

In the constructor i'm calling WatchDirectory method:
private void WatchDirectory()
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = userVideosDirectory;
watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size;
watcher.Filter = "*.mp4";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
Then the event OnChanged:
private void OnChanged(object source, FileSystemEventArgs e)
{
try
{
var info = new FileInfo(e.FullPath);
fileforupload = info.FullName;
if (e.ChangeType == WatcherChangeTypes.Changed)
{
var theSize = info.Length;
label2.BeginInvoke((Action)(() =>
{
label2.Text = theSize.ToString();
}));
}
dirchanged = true;
}
catch (Exception ee)
{
string err = ee.ToString();
}
}
Then i'm using a while loop to check when dirchange flag is true:
WatchDirectory();
while (dirchanged == false)
{
if (dirchanged == true)
{
Youtube_Uploader youtubeupload = new
Youtube_Uploader(fileforupload);
break;
}
}
The problem is that sometimes it's never changes the dirchanged to true on the OnChanged event. Not sure why. It seems to fire the OnChanged event but sometimes it doesn't execute the dirchanged = true;
Therefore inside the while loop dirchanged flag remains false all the time.
I added now a new method i called it IsFileLocked:
protected virtual bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
return true;
}
finally
{
if (stream != null)
stream.Close();
}
return false;
}
And i use this in the event OnChanged:
private void OnChanged(object source, FileSystemEventArgs e)
{
try
{
var info = new FileInfo(e.FullPath);
fileforupload = info.FullName;
IsFileLocked(info);
if (e.ChangeType == WatcherChangeTypes.Changed)
{
var theSize = info.Length;
label2.BeginInvoke((Action)(() =>
{
label2.Text = theSize.ToString();
}));
}
dirchanged = true;
}
catch (Exception ee)
{
string err = ee.ToString();
}
}
And in the method IsFileLocked i'm getting exception:
The process cannot access the file 'C:\Users\bout0_000\Videos\My Great Game - My Great Capture - 2015-08-10 14-22-52.mp4' because it is being used by another process.
I'm using external program that create the file and since the program still working on creating the file the watcher can't get to it.
So i have a confilct here from one side i want to know to watch when the file is ready finished created but on the other side i can't know since the external program still working on it.
So how can i find out when the external program finished working on the file and the file is ready ?
This is the whole part of the code of the while:
if (request.QueryString[0] == "stop")
{
dirchanged = false;
StartRecrod();
result = "Recording stopped and preparing the file to be shared on youtube";
WatchDirectory();
while (dirchanged == false)
{
if (dirchanged == true)
{
string ttttt = "ok";
break;
}
}
}
I added a string ttttt just for testing.
Sometimes it's getting to the string ttttt when using a break point and sometimes not.
In my program when i touch my android screen it send command to the pc web server and it's getting here but someting is wrong with the while loop and the flag dirchanged sometimes it does enter the while and the IF and does the string ttttt and sometimes it dosen't.
This is what i did now with the await:
TaskCompletionSource<bool> sy;
public async void SendResponse(HttpListenerRequest request)
{
string result = "";
string key = request.QueryString.GetKey(0);
if (key == "cmd")
{
if (request.QueryString[0] == "nothing")
{
return "Connection Success";
}
if (request.QueryString[0] == "start")
{
StartRecrod();
result = "Recording started";
}
if (request.QueryString[0] == "stop")
{
dirchanged = false;
StartRecrod();
result = "Recording stopped and preparing the file to be shared on youtube";
sy = new TaskCompletionSource<bool>();
WatchDirectory();
await sy.Task;
Youtube_Uploader youtubeupload = new Youtube_Uploader(fileforupload);
}
}
else
{
result = "Nothing have been done";
}
if (Youtube_Uploader.fileuploadedsuccess != null && Youtube_Uploader.fileuploadedsuccess != "")
{
result = Youtube_Uploader.fileuploadedsuccess;
}
return result;
}
But some problems.
First i'm getting errors over all the returns.
Error 2 Since 'Automatic_Record.Form1.SendResponse(System.Net.HttpListenerRequest)' returns void, a return keyword must not be followed by an object expression
And error when init my web server:
WebServer ws = new WebServer(SendResponse, "http://+:8098/");
On the SendResponse i'm getting:
Error 1 'void Automatic_Record.Form1.SendResponse(System.Net.HttpListenerRequest)' has the wrong return type
This errors happen now when changed the method to async.
This is my WebServer method that i get error when init it since it should get something else then async:
public WebServer(Func<HttpListenerRequest, string> method, params string[] prefixes)
: this(prefixes, method) { }
public void Run()
{
ThreadPool.QueueUserWorkItem((o) =>
{
Console.WriteLine("Webserver running...");
try
{
while (_listener.IsListening)
{
ThreadPool.QueueUserWorkItem((c) =>
{
var ctx = c as HttpListenerContext;
try
{
string rstr = _responderMethod(ctx.Request);
System.Diagnostics.Trace.Write(ctx.Request.QueryString);
//ctx.Request.QueryString
byte[] buf = Encoding.UTF8.GetBytes(rstr);
ctx.Response.ContentLength64 = buf.Length;
ctx.Response.OutputStream.Write(buf, 0, buf.Length);
System.Data.SqlClient.SqlConnectionStringBuilder builder = new System.Data.SqlClient.SqlConnectionStringBuilder();
}
catch { } // suppress any exceptions
finally
{
// always close the stream
ctx.Response.OutputStream.Close();
}
}, _listener.GetContext());
}
}
catch { } // suppress any exceptions
});
}
This code is horribly broken. Yes, dirchanged is always false inside the while loop, because if it becomes true you won't be in the while loop any longer.
In addition, your code blocks events from occurring, which may block the file watcher event itself, and also is not optimization safe. Use proper synchronization, here's an example:
TaskCompletionSource<bool> sy;
private void OnChanged(object source, FileSystemEventArgs e)
{
sy.SetResult(true);
}
and wait with
sy = new TaskCompletionSource<bool>();
WatchDirectory();
await sy.Task; // or sy.Task.Wait()
(You'll need to use the async keyword on the method containing that code).
This fixes all the problems you had before -- it doesn't burn CPU cycles, it continues processing Windows messages, and it won't break if the compiler chooses to cache variables in registers.
dirchanged could be getting set to true just after evaluating the inner if block. Then, next loop it breaks out without ever running your uploader.
So you have two main questions?
1.) Why is dirchanged not being set to true?
and the apparent cause...
2.) How do you use FileSystemWatcher to only act on a file that's available for edit?
FileSystemWatcher is known for being a little touchy, and I agree with your diagnosis that file access is probably the culprit. An unpredictable file access error is exactly what I would expected from a FileSystemWatcher trying to do something with a file that was just modified. Can you edit the code that's creating the file? If so, one method I've used with FileSystemWatcher is to have it only watch for file creation of a fictitious file type such as ".fsw". The program creating the file will then rename it to ".fsw" whenever it is done editing it, that way the FileSystemWatcher only gets called when it has a file available to act upon, and it can then rename the file to it's actual type. Also, if you can edit the creation code, make sure that you are doing everything you can to release the file from there. I've seen this behavior before because I forgot to close a TextWriter.
Also, I would move the line
dirchanged = true;
Outside of the try statement. Why have it in there since it definitely wont throw an error? Also, your catch statement isn't really doing error handling. Any error in your try statement and you get ejected before reaching the dirchanged = true line without being alerted that this is what happened. Have you tested your delegate code on its own? Is there a need to have the if statement for type = changed right there? If you're troubleshooting, I would consider limiting your try statement content or moving it to after the while loop as much as possible.
Also, wouldn't this be a lot more simple for your while statement?
while (dirchanged == false){}
Youtube_Uploader youtubeupload = new Youtube_Uploader(fileforupload);
It's not the most elegant solution, but one work around is to simply wait if you know the program creating/editing the file is going to close it very soon...
while (dirchanged == false){}
System.Threading.Thread.Sleep(1000);
Youtube_Uploader youtubeupload = new Youtube_Uploader(fileforupload);
EDIT: Better yet, rather than a while statement use Ben Voigt's suggestion of a TaskCompletionSource. You'll still have to deal with the file being locked but you should be able to do that after the "task" has been flagged as completed.

How to process files in directory concurrently in .net

I'm having issues processing files in parallel within a directory. I've read several similar questions and examples but I can't seem to find why my code causes exception.
My directory gets populated by other processes and will contain thousands of files at any one time. Each file has to be parsed and validated which takes time filesystem/network io etc. I need this step to be done in parallel, the rest has to be done serially.
Here's my code:
public void run()
{
XmlMessageFactory factory = new XmlMessageFactory();
DirectoryInfo dir = new DirectoryInfo(m_sourceDir);
Dictionary<string, int> retryList = new Dictionary<string, int>();
ConcurrentQueue<Tuple<XmlMsg,FileInfo>> MsgQueue = new
ConcurrentQueue<Tuple<XmlMsg,FileInfo>>();
//start worker to handle messages
System.Threading.ThreadPool.QueueUserWorkItem(o =>
{
XmlMsg msg;
Tuple<XmlMsg, FileInfo> item;
while (true)
{
if (!MsgQueue.TryDequeue(out item))
{
System.Threading.Thread.Sleep(5000);
continue;
}
try
{
msg = item.Item1;
/* processing on msg happens here */
handleMessageProcessed(item.Item2, ref retryList);
}
catch (Exception e)
{
//if this method is called it gives the
//exception below
handleMessageFailed(item.Item2, e.ToString());
}
}
}
);
while (true)
{
try
{
FileInfo[] files = dir.GetFiles(m_fileTypes);
Partitioner<FileInfo> partitioner = Partitioner.Create(files, true);
Parallel.ForEach(partitioner, f =>
{
try
{
XmlMsg msg = factory.getMessage(messageType);
try
{
msg.loadFile(f.FullName);
MsgQueue.Enqueue(new Tuple<XmlMsg, FileInfo>(msg, f));
}
catch (Exception e)
{
handleMessageFailed(f, e.ToString());
}
}
});
}
}
}
static void handleMessageFailed(FileInfo f, string message)
{
//Erorr here:
f.MoveTo(m_failedDir + f.Name);
//"The process cannot access the file because it is
//being used by another process."} System.Exception {System.IO.IOException}
}
Using ConcurrentQueue how can it end up attempting to access a file twice at the same time?
I have a test setup currently with 5000 files and this will happen at least once per run and on a different file each time. When I inspect the directory, the source file causing exception will have already been processed and is in the "processed" directory.
After a fair bit of head scratching the problem turned out to be annoyingly simple! What was happening was the parallel processing of the files in the directory was completing before the serial activity on the file, so the loop was restarting and re-adding some of the files to the Queue that were already in there.
For completeness here's the modified section of code:
while (true)
{
try
{
FileInfo[] files = dir.GetFiles(m_fileTypes);
Partitioner<FileInfo> partitioner = Partitioner.Create(files, true);
Parallel.ForEach(partitioner, f =>
{
try
{
XmlMsg msg = factory.getMessage(messageType);
try
{
msg.loadFile(f.FullName);
MsgQueue.Enqueue(new Tuple<XmlMsg, FileInfo>(msg, f));
}
catch (Exception e)
{
handleMessageFailed(f, e.ToString());
}
}
});
//Added check to wait for queue to deplete before
//re-scanning the directory
while (MsgQueue.Count > 0)
{
System.Threading.Thread.Sleep(5000);
}
}
}
I suspect a problem in XmlMsg.loadFile()
I think that you may have code like this in it:
public void loadFile(string filename)
{
FileStream file = File.OpenRead(filename);
// Do something with file
file.Close();
}
If an exception occurs in the "do something with file" part, the file won't be closed because file.Close() will never be executed. Then you'll get the "file in use" exception inside handleMessageFailed().
If so, the solution is to access the file in a using block as follows; then it will be closed even if an exception occurs:
public void loadFile(string filename)
{
using (FileStream file = File.OpenRead(filename))
{
// Do something with file
}
}
But assuming that this does turn out to be the problem, when you start using real files produced by external processes, you may have another issue if the external processes still have the files open when your worker threads try to process them.

Categories