question

CB-5320 avatar image
0 Votes"
CB-5320 asked SimpleSamples commented

How to use System.IO.File.Open, yet be able to AppendText?

My app updates a log .csv file periodically. I read the docs which suggested this approach:

                 using (StreamWriter sw = File.AppendText(filenameWithPath))
                 {
                     sw.WriteLine(someText);
                 }

Works fine. The problem is the user wants to open the log, when they do the app crashes because the files locked.
I've been trying to work out what to do about that. I want to hold it open so I tried this:

                 fs =File.Open(filenameWithPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);

sr = new StreamWriter(fs);


This creates the file, locks it to others, perfect.. except!!!

sr.WriteLine(data); doesnt write any data! The file is always empty.


What could be going wrong, or what is the right way to do this using the using statement?
Thanks

dotnet-csharp
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


Maybe try FileMode.Append instead of FileMode.OpenOrCreate. How to reproduce the problem?

0 Votes 0 ·
SimpleSamples avatar image
0 Votes"
SimpleSamples answered SimpleSamples commented

Are you flushing the file, as in StreamWriter.Flush? The using statement does that automatically so that is probably why you are not familiar with flush. StreamWriter.Close will also flush. The documentation of that says that implementation of Close calls the Dispose method also. The Dispose method is important; without calling it the file remains locked and other applications will be unable to open the file (until it is disposed, including the end of the application).


· 6
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

I made a test case to work this out.

Your comment is what lead me to get something to appear in the file. Its like an order of operations problem.

             fs = File.OpenWrite(x);

             sr = new StreamWriter(fs);

             sr.WriteLine("Who Greased the Grapevine?");

             sr.Flush();
             fs.Close();

1) I used OpenWrite instead of Open
2) I added the Flush, then Close whereas before I had a Flush, then Dispose.

Whatever these all do, that's the order that worked.
For the user to view the log in progress... I guess I will use a File.Copy operation. So it checks if the file is locked, closes, flushes, copies, then resumes.
With all this .net history, its surprising its not all baked into one class instead of strung out in all these different error prone details.

0 Votes 0 ·

It seems that you are not interested in appending the text to the end of existing log files.

0 Votes 0 ·

You are correct; my test case example no longer did append. I will probably change it to be

fs =File.Open(filenameWithPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);

So the user can read the log while its being logged but cant lock it. About append, that was just how the .docs example did it. In my app I give each log file a unique name with a timestamp, so appending to a master isnt really needed. But I get you. If I want to keep extending one file I use Append.

But you can anticipate the next problem, if File.OpenText(file) worked for me, will File.Open(.... blah blah) still work? Who knows. FileMode.OpenText isnt an option... I call that confusing and inconsistent.

0 Votes 0 ·

Yes the .Net requirement to flush is rather unusual. Most other file libraries (classes or whatever) do the flush automatically, at least in the close. The requirement to dispose of the file object (to free it for other applications) seems unusual for C# programmers because most objects are disposed of automatically by garbage collection.

0 Votes 0 ·

May I ask a quick follow up on that point, as I just ran into this?

The logging can be stopped by the user with a button, and I also wanted it stopped ( flushed and closed) if the app is exited (red x).
I found I could not simply Flush twice because if the FileStream is closed, you get an error.

So.. I went "How can I write a conditional check to see if fs is closed or not to avoid this?" I don't see any obvious method in FileStream to find out. Like IsOpen, or IsClosed, or ... that kind of thing

How would you avoid that trap?

0 Votes 0 ·
Show more comments
karenpayneoregon avatar image
0 Votes"
karenpayneoregon answered CB-5320 commented

Conceptually here are two code samples, first will get a in use error while the second does not.

 [TestMethod]
 [TestTraits(Trait.FileOperations)]
 [ExpectedException(typeof(IOException), "user message.")]
 public async Task FileInUseExample2()
 {
    
     if (!File.Exists(FileInUseName))
     {
         File.Create(FileInUseName);
     }
    
     await using StreamWriter writer = new StreamWriter(FileInUseName);
     await writer.WriteLineAsync("Hello");
        
 }
    
 /// <summary>
 /// Create and write to a file done right as appose to <see cref="FileInUseExample2"/>
 /// </summary>
 /// <returns>Nada</returns>
 [TestMethod]
 [TestTraits(Trait.FileOperations)]
 public async Task FileInUseExample1()
 {
    
     File.Create(FileInUseName).Close();
    
     await Task.Delay(500);
        
     await File.WriteAllTextAsync(FileInUseName, "Hello");
    
 }
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


I do not really understand how your code works. But thanks for looking at it.

0 Votes 0 ·