user1404577
user1404577

Reputation:

Will using System.IO.StreamWriter & WriteLineAsync wait for a file that is being accessed by another thread

I have the following code inside my asp.net mvc-5 web application :-

System.IO.StreamWriter file =
new System.IO.StreamWriter(Server.MapPath("~/logs/logs.txt"), true);
await file.WriteLineAsync("execution started at " + System.DateTime.Now.ToString());
file.Close();

now my question what will happen if 2 methods execute the above code at the same time? will one of them fail ? since it will be trying to write to the txt file which is being accessed by another thread ?

Upvotes: 0

Views: 3644

Answers (3)

spender
spender

Reputation: 120410

You can leverage TPL Dataflow to do this very nicely for you such that you get a queue and a mechanism by which writes to the file never occur in parallel (by using MaxDegreeOfParallelism)

public class Logger
{
    private ActionBlock<string> block;
    public Logger(string filePath)
    {
        block = new ActionBlock<string>(async message => {
            using(var f = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write))
            {
                f.Position = f.Length;
                using(var sw = new StreamWriter(f))
                {
                    await sw.WriteLineAsync(message);
                }
            }
        }, new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism = 1});
    }
    public void Log(string message)
    {
        block.Post(message);
    }
}

Upvotes: 4

Scott Chamberlain
Scott Chamberlain

Reputation: 127543

When you open StreamWriter with a string it does the following to create the stream

private static Stream CreateFile(String path, bool append, bool checkHost) {
    FileMode mode = append? FileMode.Append: FileMode.Create;
    FileStream f = new FileStream(path, mode, FileAccess.Write, FileShare.Read,
        DefaultFileStreamBufferSize, FileOptions.SequentialScan, Path.GetFileName(path), false, false, checkHost);
    return f;
}

Because it does FileAccess.Write, FileShare.Read that means it is asking for write permission and only allow other people to open the file if they are asking for read permission. Because both calls will be asking for write permission the 2nd call will fail with a "file in use" error.


A way to work around this issue is use a SemaphoreSlim to block access to the file, this will only allow one writer at a time.

//Elsewhere
private static SemaphoreSlim _fileLock = new SemaphoreSlim(1);


//In your function.
try
{
    await _fileLock.WaitAsync();
    using(var file = new System.IO.StreamWriter(Server.MapPath("~/logs/logs.txt"), true))
    {
        await file.WriteLineAsync("execution started at " + System.DateTime.Now.ToString());
    }
}
finally
{
    _fileLock.Release();
}

However a even better solution is just find a 3rd party logging library that can handle this for you like Log4Net, then you don't need to worry about the locking or concurrency.

Upvotes: 2

TheValyreanGroup
TheValyreanGroup

Reputation: 3599

Yes. You will get an error that the file is in use by another process. Your best bet is to surround that block withtry catch and handle the exception then retry once the file is available.

Upvotes: 0

Related Questions