views:

118

answers:

4

I have two C# applications, one is reading a file (File A) line by line and writing its contents to a different file (File B).

The second application is using FileSystemWatcher for File B to see when it is updated and reporting the difference is line numbers between when the program was started and when the file was changed.

Thats all I am trying to do for now, ultimately I want to read the lines between when the file was last read and the current read but until I can get the line difference that is on hold.

The code that I have for application 1 is;

        static void Main(string[] args)
    {
        String line;

        StreamReader sr = new StreamReader("f:\\watch\\input.txt");

        FileStream fs = new FileStream("f:\\watch\\Chat.log", FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
        StreamWriter sw = new StreamWriter(fs);

        while ((line = sr.ReadLine()) != null)
        {
            sw.WriteLine(line);
            Thread.Sleep(200);
            Console.WriteLine(line);
            sw.Flush();

        }

        sw.Close();
        sr.Close();

    }

The code that I have for application 2 is;

        public static int lines = 0;

    public static void Main()
    {
        Run();
    }

    public static void Run()
    {
        string[] args = System.Environment.GetCommandLineArgs();

        if (args.Length != 2)
        {
            Console.WriteLine("Usage: Watcher.exe (directory)");
            return;
        }

FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = args[1];

watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite 
   | NotifyFilters.FileName | NotifyFilters.DirectoryName;

watcher.Filter = "Chat.log";

watcher.Changed += new FileSystemEventHandler(OnChanged);

watcher.EnableRaisingEvents = true;

lines = File.ReadAllLines(args[1] + "\\Chat.log").Length;

Console.WriteLine("File lines: " + lines);

while(Console.Read()!='q');
}

private static void OnChanged(object source, FileSystemEventArgs e)
{
    Linework(e.FullPath);
    Console.WriteLine("File: " +  e.FullPath + " " + e.ChangeType);
}

public static string Linework(string path)

{



   string newstring = " ";

using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    int newlines = File.ReadAllLines(path).Length;
    Console.WriteLine("Lines now: " + newlines);
}

return newstring;

}

Now when I try and run these two applications together I get an exception saying "Unhandled Exception: System.IO.IOException: The process cannot access the file because it is in use by another process".

I have both filestreams setup for ReadWrite access and I have one of the filestreams setup for FileAccess.Write and the other for FileAccess.Read.

Any clues as to why I would be getting this exception?

Thanks Hew.

+2  A: 

MSDN offers two ways to not obtain an exclusive hold:

A FileStream object will not have an exclusive hold on its handle when either the SafeFileHandle property is accessed to expose the handle or the FileStream object is given the SafeFileHandle property in its constructor.

The documentation implies that the inverse is true: Opening a FileStream without setting the SafeFileHandle means the FileStream maintains an exclusive hold on the file handle (which is inline with the IO exception that is supposed to be thrown).

A quick google pulled up no boilerplate code and I honestly don't want to write two programs for free so I'll leave it to you to write the implementation to prove my theory.

P.Brian.Mackey
The SafeHandle is usually exposed by network Socket operations or file mapping (no code exposing a file handle is used in the application). Where does the documentation imply that?
Jaroslav Jandek
Its implied in the quote. Take the inverseA fileStream object will have an exclusive hold on its handle when neither the the SafeFileHandle property is accessed to expose the handle or the FileStream object is given the SafeFileHandle property in its constructor.
P.Brian.Mackey
Simple sentential calculus:Sentence A: **FS will NOT have an exclusive hold.**Sentence B: **SafeFileHandle is used.**You used `(B => A)` and `(~B => ~A)` which is not equivalent.`(B => A)` and `(~A => ~B)` is equivalent.The original sentence is quivalent to: *If SafeFileHandle is used, FS will NOT have an exclusive hold.*So the equivalent inversed implication is: *(SafeFileHandle is **NOT** used) when (FS has exclusive hold)*which is equivalent with: *(If FS has exclusive hold), (SafeFileHandle is **NOT** used)*.It means the sentence in your answer is incorrect!
Jaroslav Jandek
Bottom line - the text allows for **possibility** that FS does NOT have an exclusive hold even when SafeHandle is not used.
Jaroslav Jandek
Note the word "theory" in my statement. Be practical here. Microsoft did not write the documentation based on calculus.
P.Brian.Mackey
+1  A: 
StreamReader sr = new StreamReader("f:\\watch\\input.txt");

input.txt might not be available for reading?

Also use the using statement instead of Close() in the 1st application (in case an exception is thrown).

Otherwise it is OK. The file share may require additional permissions though (can't really affect that).

I have missed one piece of code:

int newlines = File.ReadAllLines(path).Length;

use the stream with a StreamReader for that.

Jaroslav Jandek
@jaroslav Thanks for the tip about using "using" I changed the app to do that. The permissions should be fine as I can open input.txt. I am running with full access to the system.
Hew Masters
For the `input.txt` I meant if it isn't open by another process without sharing.
Jaroslav Jandek
+3  A: 

Allowing the second program ReadWrite access on the file would work in this case.

//lines = File.ReadAllLines(args[1] + "\\Chat.log").Length;
//Commenting the above lines as this would again open a new filestream on the chat.log
//without the proper access mode required.


    using (FileStream fsReader = new FileStream(args[1] + "\\Chat.log", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
        {
            using (StreamReader sr = new StreamReader(fsReader))
            {
                while (sr.ReadLine() != null)
                    lines++;
            }
        }
Bharath K
+1 Exactly what the OP needs. Instead of `args[1] + "\\Chat.log"` he will use `path` since he has it already defined in scope.
Jaroslav Jandek
+5  A: 

lines = File.ReadAllLines(args[1] + "\Chat.log").Length;

There's your problem. That method opens the file, reads all the lines and closes it again. It uses "normal" file share settings when opening the file, FileShare.Read. That denies write access to any other process that also has the file opened.

That cannot work here, you've already have the file opened with write access. The 2nd process cannot deny it. The IOException is the result.

You cannot use File.ReadAllLines() as-is here, you need to open a FileStream with FileShare.ReadWrite, pass it to a StreamReader and read all lines.

Beware the very troublesome race potential you've got here, there's no guarantee that the last line you'll read is a complete line. Getting only a \r and not the \n at the end of the line is a particularly tricky issue. This will strike randomly and infrequently, the hardest bugs to troubleshoot. Maybe your Flush() call fixes it, I've never been brave enough to put this to the test.

Hans Passant
+1 impressive. I saw something along those lines, but couldn't put it into words.
P.Brian.Mackey
It has been already written in the other answers. As for the reads, you are right. He could use a named Mutex to fix that issue easily.Also he does not really reads the lines (as in their contents), just counts them.
Jaroslav Jandek
Thank you Hans, I tried using a Streamreader and it did work however I assumed that reading each of the lines to calculate a total number of lines to compare would be inefficient. I am going to go with this as I have been researching how to remove SafeFileHandles and it does not seem like an option.Thank you very much.
Hew Masters
It is just as in/efficient as File.ReadAllLines(). No way to make it more efficient, there's only one way to read a text file under the hood. The file data ought to come out of the file system cache anyway.
Hans Passant