Stanislav Čaja
Stanislav Čaja

Reputation: 108

How to solve probable race condition with Mutex

I have the class ComputationService which is the wrapper of the old legacy COM object. This COM object cannot have multiple instances because of how its implemented. In the application, there are two buttons (for simplification) when clicked a new Task runs which use a ComputationService these tasks are kind of long-running tasks so you can click on the second button when the first one is in the process of computing. That's where the problem occurs because I can't let both threads use the ComputationService so I decided to use Mutex to allow only one instance of ComputationService at the time.

What I have:

public class ComputationService : IComputationService
{
       public ComputationService()
       {
           _mutex = new Mutex(true, "AwesomeMutex");
           _mutex.WaitOne();
       }
       public void Dispose()
       {
           _mutex.ReleaseMutex();
       }
}

and then two places where I use it:

public Task Compute1()
{
   return Task.Run(() =>
   {
       foreach (var item in items)
       {
            using (var service = _computationFactory()) // new instance of ComputationService
            {
               //Do some work
            }
       }
   });
 }

public Task Compute2()
{
   return Task.Run(() =>
   {
       foreach (var item in items)
       {
            using (var service = _computationFactory()) // new instance of ComputationService
            {
               //Do some other work
            }
       }
   });
 }

What I get when I am debugging to console:

Mutex created by Thread: 18
Wait, thread: 18
Do Work, thread: 18
Mutex created by Thread: 20
Wait, thread: 20
Mutex release by thread: 18

Mutex created by Thread: 18
Wait, thread: 18
Do Work, thread: 18
Mutex release by thread: 18

Then of course AbandonedMutexException() occurs on Thread 20.

I tried to use lock in the constructor of ComputationService but did not work.

Can you please help me, what I am doing wrong and how to fix it?

Upvotes: 3

Views: 224

Answers (1)

Evk
Evk

Reputation: 101623

Problem is here:

_mutex = new Mutex(true, "AwesomeMutex");
_mutex.WaitOne();

You are passing true as first argument, named initiallyOwned. If you pass true there - that means IF mutex with such name did not exist before and is created as a result of this constructor - current thread now owns it. You don't check if mutex was created as a result of this call, and actually there is even no way to check that when using this constructor overload. So never ever pass true if using this constructor.

If you want to utilize initiallyOwned - then use another overload, which actually tells you whether mutex was created as a result of this constructor:

_mutex = new Mutex(true, "AwesomeMutex", out var createdNew);

Now, if createdNew returned true - you do NOT need to call _mutex.WaitOne(), because as per above - current thread already owns the mutex. That's what happens in your case - sometimes you already own the mutex and then you call _mutex.WaitOne(). Now to release it you would have to call Release() twice, which you never do.

Alternatively, just pass false, then wait:

_mutex = new Mutex(false, "AwesomeMutex");
_mutex.WaitOne();

If the whole thing happens in the same process, then you don't need global mutex (which you now have because you pass a name) - you can do fine with a local one:

public class ComputationService : IDisposable {
    // one static instance
    private static readonly Mutex _mutex = new Mutex();
    public ComputationService()
    {
        _mutex.WaitOne();
    }
    public void Dispose() {
        _mutex.ReleaseMutex();
    }
}

There are other improvements that can be made, but they are out of scope of this question.

As mentioned in comments - in single process lock (Monitor.Enter \ Monitor.Exit) will be faster and achieve the same result in your case:

public class ComputationService : IDisposable {
    // one static instance
    private static readonly object _mutex = new object();
    public ComputationService()
    {
        Monitor.Enter(_mutex);
    }
    public void Dispose() {
        Monitor.Exit(_mutex);
    }
}

Upvotes: 4

Related Questions