I have a multi threaded C# application which creates files, opens them for processing, then deletes them once finished. This application can expect anywhere from 1 - 100 files to be processed. Somewhat randomly (most likely attributed to the multi threaded nature of the application) I get a sharing violation when I try to delete a file after processing. My gut says, well Vic, you didn't properly close/dispose the file before trying to delete it. And I would go with my gut if it happened for every file, but it doesn't. So, I'm trying to find out where I'm making a mistake. Anyone out there have any pointers on how to debug this type of an exception? I would love to see a stack trace on the file if that makes sense.
I'll attempt to show pseudo code, however, my question is more on how to debug this type of exception:
Application Events:
Operation Start += Create new processor.
Transfer File += Processor.ProcessFile and Add new Document object to Processor's Document collection (as path, not file)
Operation complete += Processor.Aggregate files, create new File containing contents of files. When this method is finished, it calls ProcessorFinished.
Processor Events:
Processor Finished += Application_CleanUpProcessor. In this method, I dispose of the processor, which in turn disposes of a document object, which deletes the file.
Accepting that you need a quick way to verify whether or not to spend more time on debugging your code, or maybe writing good tests to prove your code is ok, what you want is a quick way to prove that no other process is using your file. So, lets assume:
You want to be able to run your program, and see what has happened to that file leading up to the point you have a sharing violation.
I would do this:
Procmon is an excellent tool, you can filter down to what you want to see happening across all processes in sequential order. Link to procmon at sysinternals, from Microsoft
Open procmon, add a filter for "Path" "begins with" ""
Now add a highlight for "Result" "is" "SHARING VIOLATION"
And finally, run your program until you get an exception, then right click the file with the sharing violation, in the path column, and select "Include '<filename here>'" to remove all other results. You can now see all activity for the file that caused your exception ...
If you want to get comfortable with procmon, here's the code I used to fake this all for you. It has a side thread which locks the file, and a main thread which then tries to lock the file. Just create a C# console app and off you go. It looks like this:
So in less than 2 minutes you can see if its your code at fault, or something else. I used this the other day to determine that my Com component was in fact using alternate file streams, and so threw an exception when it was trying to use a network drive. No amount of unit testing would have helped me there.
And here is the test code to force a sharing violation:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using System.Threading;
internal class Program
{
private static int lockPoint = 0;
private static void Main(string[] args)
{
const string testFile = @"H:\test\test.txt";
FileInfo testFileInfo = new FileInfo(testFile);
if (!testFileInfo.Directory.Exists)
{
testFileInfo.Directory.Create();
}
// Clear our example
if (testFileInfo.Exists)
{
testFileInfo.Delete();
}
// Create the test file
using (FileStream fs = File.Create(testFile))
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine("test file content");
}
Task iLockTheFileFirst = new Task(() => {
using (FileStream fsThread = File.Open(testFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
Console.WriteLine("iLockTheFileFirst: I opened the file");
// Set lockPoint to 1 and let main try and open the file
Interlocked.Exchange(ref lockPoint, 1);
// Wait until the main thread sets lockPoint to 3
const int ifEqualTo3 = 3;
const int replaceWith4 = 4;
while (Interlocked.CompareExchange(ref lockPoint, replaceWith4, ifEqualTo3) != ifEqualTo3)
{
Console.WriteLine("iLockTheFileFirst: Waiting for main thread to let me finish");
Thread.Sleep(1000);
}
}
Console.WriteLine("iLockTheFileFirst: I have closed the file");
});
// Start the thread and lock the file
iLockTheFileFirst.Start();
// Now spin until the lockPoint becomes 1
const int ifEqualTo1 = 1;
const int replaceWith2 = 2;
// If lockPoint is equal to 1 (i.e. the main thread wants us to finish), then move it to 2
while (Interlocked.CompareExchange(ref lockPoint, replaceWith2, ifEqualTo1) != ifEqualTo1)
{
Console.WriteLine("Main thread: waiting for iLockTheFileFirst to open the file");
Thread.Sleep(1000);
}
try
{
Console.WriteLine("Main thread: now I'll try opening the file");
using (FileStream fsMain = File.Open(testFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
Console.WriteLine("Main thread: I opened the file, which shouldn't be possible");
}
}
catch (IOException ioex)
{
Console.WriteLine("Main thread: IOException: " + ioex.Message);
}
catch (Exception ex)
{
Console.WriteLine("Main thread: some other exception: " + ex.Message);
}
// Set lockPoint to 3 and let other thread finish
Interlocked.Exchange(ref lockPoint, 3);
// Wait for other thread to finish
const int ifEqualTo4 = 4;
const int replaceWith5 = 5;
while (Interlocked.CompareExchange(ref lockPoint, replaceWith5, ifEqualTo4) != ifEqualTo4)
{
Thread.Sleep(10);
}
Console.WriteLine("Main thread: Press enter to finish");
Console.ReadLine();
}
}
That's all folks!