JianYA
JianYA

Reputation: 3024

c# Writing text to one text file from multiple threads

I have an exe that gets called multiple times per second and within that exe, there is a function that writes to a textfile as a log.

This is my Logging class:

public static class Log
{
    private static ReaderWriterLockSlim lock_ = new ReaderWriterLockSlim();
    public static void Output(string Input)
    {
        string MyDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
        string logPath = @MyDocuments + "\\SCV";
        lock_.EnterWriteLock();
        Directory.CreateDirectory(logPath);
        string logFilePath = @logPath+"\\SCVLog -" + DateTime.Today.ToString("MM-dd-yyyy") + "." + "txt";
        FileInfo logFileInfo = new FileInfo(logFilePath);
        DirectoryInfo logDirInfo = new DirectoryInfo(logFileInfo.DirectoryName);
        if (!logDirInfo.Exists) logDirInfo.Create();
        try
        {
            using (FileStream fileStream = new FileStream(logFilePath, FileMode.Append))
            {
                using (StreamWriter log = new StreamWriter(fileStream))
                {
                    log.WriteLine(Input);
                }
            }
        }
        finally
        {
            lock_.ExitWriteLock();
        }
    }
}

This is how I call it:

Log.Output(finalFile + " processed");

It seems like the logging class is thread safe, however, I still get this error:

The process cannot access the file because it is being used by another process

What else do I need to do to make this thread safe?

Upvotes: 1

Views: 1444

Answers (2)

Charles Owen
Charles Owen

Reputation: 2840

I have an implementation that has 16 concurrent threads (ThreadPool) writing to a single file. I tried the async route but never got a satisfactory result. It gets tricky to determine where to block. What I have working is a combination of method Synchronization and ReaderWriterLockSlim. It took this combination to properly synchronize the thread.

BTW, don't be afraid of this annotation:

[MethodImpl(MethodImplOptions.Synchronized)]

This is similar to a compiler hint and will wrap the method with a lock statement, which is probably what you'd want to do anyway.

lock (_lock) {
    SaveTextToFile();
}

ReaderWriterLockSlim is a construct that is made especially for this use case, where we are concerned about multiple concurrent threads accessing a file. This class has been optimized and works very well.

I'm writing 250,000 records to a text file in a 24-hour period so you really have to know what you're doing or you will have a royal mess with all the file/thread lock contention. NOTE: I'm planning on writing to a queue in the future, such as NATS, which will offer far better performance and less contention.

Timing is important here. I close the file BEFORE exiting the lock. I also do not Dispose of the file at all. Dispose seemed to cause some errors and Close will call Dispose in its method.

[MethodImpl(MethodImplOptions.Synchronized)]//Same as putting a lock around 
public void SaveTextToFile(StringBuilder sb)
{
   ReaderWriterLockSlim slim = new ReaderWriterLockSlim();
   slim.EnterWriteLock();
   StreamWriter writer = null;

try {
    writer = File.AppendText(path);                
    writer.Write(sb?.ToString());
}
finally {
    writer?.Close();
    logger.LogInfo("TextFileHelper", string.Format("{0},{1},{2},{3},{4},{5},{6}", "After file close", slim.WaitingUpgradeCount, slim.WaitingWriteCount, slim.WaitingReadCount, slim.IsReadLockHeld, slim.IsUpgradeableReadLockHeld, slim.IsWriteLockHeld));
    slim?.ExitWriteLock();
    logger.LogInfo("TextFileHelper", string.Format("{0},{1},{2},{3},{4},{5},{6}", "After exit write lock", slim.WaitingUpgradeCount, slim.WaitingWriteCount, slim.WaitingReadCount, slim.IsReadLockHeld, slim.IsUpgradeableReadLockHeld, slim.IsWriteLockHeld));
 }  
}   

Upvotes: 0

Jesper Mygind
Jesper Mygind

Reputation: 2486

I am not sure how your locking process works, but if you replace

new FileStream(logFilePath, FileMode.Append)

with

new FileStream(logFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)

I would imagine that your lock_ is unnecessary.

Upvotes: 1

Related Questions