Reputation: 3024
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
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
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