1

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?

4
  • Are you reading from this file? The fact that you are using ReaderWriterLockSlim would point to the fact your are, otherwise you would use another synchronization primitive.. And if you are, are you sure you are opening the file in the correct share mode. In short I think this question maybe lacking some vital information Commented Jul 10, 2020 at 7:50
  • 1
    I'd suggest using a library like NLog. A lot of features including writing into one file from multiple processes. Commented Jul 10, 2020 at 7:53
  • HI @TheGeneral, I am not reading from this file. What other primitive is there? Could you use an example? This is all the code I have haha. I don't have anything else to show. Commented Jul 10, 2020 at 8:07
  • If you have no other code reading from this file then I'm starting to think another process might be at play. Anyway you can test this for your self by using a standard lock Commented Jul 10, 2020 at 8:08

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Wouldn't that still be an issue because the file is being used by another process?
No. Check out learn.microsoft.com/en-us/dotnet/api/…: FileShare.ReadWrite "Allows subsequent opening of the file for reading or writing. If this flag is not specified, any request to open the file for reading or writing (by this process or another process) will fail until the file is closed."
FileShare.Write could be enough, if you are absolutely sure you will never want to read from the file while your program is running.
0

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));
 }  
}   

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.