2

I have a function that asynchronously writes to a log file, that is called multiple times. However, I'm getting an error:

System.IO.IOException: 'The process cannot access the file '<log path>' because it is being used by another process.'

The code is here:

public async void log(string msg)
{
    await Task.Run(() => {

        // Check that log directory exists, or create one
        if (!Directory.Exists(@"log dir")) Directory.CreateDirectory(@"log dir");

        // Append to log
        using (StreamWriter w = File.AppendText(@"log path"))
        {
            w.WriteLine(DateTime.Now + " : " + msg);
            w.Close();
        }
    });
}

My understanding of async programming comes mainly from node.js, which is a single-threaded language. Am I correct in thinking that since C# is multi-threaded (and takes a multi-threaded approach to async code) that IO doesn't automatically queue for a resource as it does in node.js?

Is there an easy way to write to files asynchronously in C#? Or am I better off just making this log function synchronous, since the performance cost would be irrelevant for a few line writes...

Zach Smith
  • 8,458
  • 13
  • 59
  • 133
  • My thought process was to just fire and forget log lines. but I guess they should be waited in the code. in which case collisions are avoided – Zach Smith Aug 10 '17 at 16:06
  • 3
    Why aren't you using a logging framework that supports async https://www.nuget.org/packages/Log4Net.Async/ – johnny 5 Aug 10 '17 at 16:07

1 Answers1

2

Async Void is bad you will not catch exceptions properly, also by using task run with synchronous methods you aren't getting the full benefits of Async, you will still be blocking resources.

It's probably better to use a logging framework but if you really want you can write async like so:

private async Task WriteTextAsync(string filePath, string text)
{
    byte[] encodedText = Encoding.Unicode.GetBytes(text);

    using (FileStream sourceStream = new FileStream(filePath,
        FileMode.Append, FileAccess.Write, FileShare.None,
        bufferSize: 4096, useAsync: true))
    {
        await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
    };
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
johnny 5
  • 19,893
  • 50
  • 121
  • 195
  • Thanks - I know I should be using a logging framework.... What is the difference between your implementation of appending to a file and mine? – Zach Smith Aug 10 '17 at 16:10
  • @ZachSmith see my edits, probably best to just use a framework to avoid unforseen issues – johnny 5 Aug 10 '17 at 16:15
  • Thank you. Did you add the locking mechanism? Or is that the `async Task` in the signature? Can you still get race conditions if you await all async code? i.e. if two separate functions that are running asynchronously call `WriteTextAsync` to the same path, could that result in a race? – Zach Smith Aug 10 '17 at 16:23
  • @Nkosi, shouldn't thinks be fine with the `FileShare.None` options, won't that like the file during the write – johnny 5 Aug 10 '17 at 16:31
  • @johnny5 the lock is so file write will happen in the order they were called. – Nkosi Aug 10 '17 at 16:32
  • @johnny5 ok I have found where you got the code example from and I retract my statement about the lock. – Nkosi Aug 10 '17 at 16:40
  • @ZachSmith that should fine, but I still recommend using Log4Net or another light framework for logging. Most of those frameworks have added benefits like flushing etc, and they are easy to configure globally etc – johnny 5 Aug 10 '17 at 16:43
  • It looks like this will throw an exception as well if the file is accessed concurrently, [see here](https://stackoverflow.com/q/30799514/5951133) – Jonathan Tyson Aug 11 '17 at 01:14
  • @JonathanTyson yes that's why I've recommended to the op several times to use a logging framework – johnny 5 Aug 11 '17 at 01:30
  • @johnny5 - I opted to go with a library called `NLog` because it had more documentation. However they recommend creating one logger instance per class, but I'm doing one instance per method. (for not very many log writes). Do you think this would result in the same problem? – Zach Smith Aug 24 '17 at 12:24
  • I'm not too familiar with that library, but using a library is definitely the way to go, if your logging everything properly it's shouldn't matter much. What kind of application is this being used for – johnny 5 Aug 24 '17 at 13:29