Reputation: 109
I'm using a named mutex to lock access to a file (with path 'strFilePath') in a construction like this:
private void DoSomethingsWithAFile(string strFilePath)
{
Mutex mutex = new Mutex(false,strFilePath.Replace("\\",""));
try
{
mutex.WaitOne();
//do something with the file....
}
catch(Exception ex)
{
//handle exception
}
finally
{
mutex.ReleaseMutex();
}
}
So, this way the code will only block the thread when the same file is being processed already. Well, I tested this and seemed to work okay, but I really would like to know your thoughts about this.
Upvotes: 10
Views: 10359
Reputation: 47
For those such as myself who stumble across this thread in the future - the answer given by @gabba works fantastically (tested with accessing the same file from 2048 concurrent threads and performing writing, reading, and file deletion), but if counting the references is not important, the following code:
public class NamedLock
{
private class LockAndRefCounter
{
public long refCount;
}
private ConcurrentDictionary<string, LockAndRefCounter> locksDictionary = new ConcurrentDictionary<string, LockAndRefCounter>();
public void DoWithLockBy(string key, Action actionWithLock)
{
var lockObject = new LockAndRefCounter();
var keyLock = locksDictionary.GetOrAdd(key, lockObject);
Interlocked.Increment(ref keyLock.refCount);
lock (keyLock)
{
actionWithLock();
Interlocked.Decrement(ref keyLock.refCount);
if (Interlocked.Read(ref keyLock.refCount) <= 0)
{
LockAndRefCounter removed;
locksDictionary.TryRemove(key, out removed);
}
}
}
}
can be reduced to:
public class NamedLock
{
private class LockObject { /* Empty */ }
private static readonly ConcurrentDictionary<string, LockObject> locks = new();
public void DoWithLockBy(string key, Action actionWithLock)
{
LockObject _lock = locks.GetOrAdd(key, new LockObject());
lock (_lock)
{
action();
}
}
}
The result is an empty object for each file path - i.e. a "named" lock. Instead of removing the lock object from the dictionary, it is used by the lock
statement as a mutex for all threads that access this file path.
Upvotes: -1
Reputation: 2880
I ran into the same problem with many threads that can write in the same file.
The one of the reason that mutex not good because it slowly:
duration of call mutexSyncTest: 00:00:08.9795826
duration of call NamedLockTest: 00:00:00.2565797
BlockingCollection collection - very good idea, but for my case with rare collisions, parallel writes better than serial writes. Also way with dictionary much more easy to realise.
I use this solution (UPDATED):
public class NamedLock
{
private class LockAndRefCounter
{
public long refCount;
}
private ConcurrentDictionary<string, LockAndRefCounter> locksDictionary = new ConcurrentDictionary<string, LockAndRefCounter>();
public void DoWithLockBy(string key, Action actionWithLock)
{
var lockObject = new LockAndRefCounter();
var keyLock = locksDictionary.GetOrAdd(key, lockObject);
Interlocked.Increment(ref keyLock.refCount);
lock (keyLock)
{
actionWithLock();
Interlocked.Decrement(ref keyLock.refCount);
if (Interlocked.Read(ref keyLock.refCount) <= 0)
{
LockAndRefCounter removed;
locksDictionary.TryRemove(key, out removed);
}
}
}
}
Upvotes: 5
Reputation:
An alternative would be: make one consumer thread which works on a queue, and blocks if it is empty. You can have several producer threads adding several filepaths to this queue and inform the consumer.
Since .net 4.0 there's a nice new class: System.Collections.Concurrent.BlockingCollection<T>
A while ago I had the same issue here on Stack Overflow - How do I implement my own advanced Producer/Consumer scenario?
Upvotes: 1
Reputation: 70369
Since you are talking about a producer-consumer situation with multiple threads the "standard solution would be to use BlockingCollection
which is part of .NET 4 and up - several links with information:
IF you just want to make the locking process work then:
use a ConcurrentDictionary
in combination with the TryAdd
method call... if it returns true
then the file was not "locked" and is now "locked" so the thread can proceed - and "unlock" it by calling Remove
at the end... any other thread gets false
in the meantime and can decide what to do...
I would definitely recommend the BlockingCollection
approach though!
Upvotes: 2