I have an .ASMX
API build using c#/asp.net/.net4.7.2
and it has been set to use 16 threads in IIS
. There are various modules that write (only writing, no reading) to multiple log files like so:
2022-06-01-Wed-_ModuleA.log
2022-06-01-Wed-_ModuleB.log
I was using the usual lock() method:
public class Audit {
public static object LogWriteLocker = new object();
public void LogEntry(string path, string logText) {
lock (LogWriteLocker) {
File.AppendAllText(path, logText);
}
}
}
This should not be happening, but I was seeing errors like:
The process cannot access the file 'D:MySiteApp_Data2022-06-01-Wed-_ModuleA.log' because it is being used by another process.
So I am trying to figure our a workaround like below:
readonly private static ReaderWriterLockSlim _readWriteLock = new ReaderWriterLockSlim();
public static void WriteToFileThreadSafe(string path, string text) {
_readWriteLock.EnterWriteLock();
try {
File.AppendAllText(path, text);
}
catch (Exception exx) {
LogException(exx, "Error Writing to Log");
}
finally {
_readWriteLock.ExitWriteLock();
}
}
Sorry if there is TMI, but my questions are:
- What is wrong with my implementation of the
lock()
method? - Is
ReaderWriterLockSlim
implementation any better? I have not used this before. - One problem is using a single lock object for multiple files, where as I should have an array of lock objects/ReaderWriterLockSlim. The number of log files are dynamic so how do I do that efficiently?
Thank you.
Edit:
Contemplating a 3rd option, that has separate locks for each file path:
private static ConcurrentDictionary<string, object> LogWriteLocker = new ConcurrentDictionary<string, object>();
public static void WriteToFileThreadSafe(string path, string text)
{
//Adds a key/value pair to the ConcurrentDictionary<TKey,TValue> if the key does not already exist. Returns the new value, or the existing value if the key already exists.
var lockObj = LogWriteLocker.GetOrAdd(path, new object());
lock(lockObj)
{
File.AppendAllText(path, text);
}
}
2
Answers
In my project I had about 10 processes that were writing to some common logs. I kept getting the
UnauthorizedAccessException
:Access to the path 'Globalmymutexid' is denied.
intermittently. So I ended up changing the constructor as below (idea from here) :I have finally understood that my mistake was thinking of this in terms of "milti-threading" were as the issue was due to "multi-process". All the variations I have listed in the OP end up with lock() at the core, which works only for single process, multi thread situation.
Thanks to @Eldar and just an addition to @Charlieface answer, here is what I am using for my cause. I use the fileName as mutex name so that each file has a separate lock (as opposed to using one lock for all files)
Looks like you are writing to the file across different processes. In which case you need a global mutex.
The
ReleaseMutex
call must happen on the same thread asWaitOne
, so if you useasync
you need to make sure to marshal back to the original thread using aSynchonizationContext
.