Jurgen
Jurgen

Reputation: 109

Using a named mutex to lock a file

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

Answers (4)

Coccho
Coccho

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

gabba
gabba

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

user57508
user57508

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

Yahia
Yahia

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

Related Questions