1

I'm trying to wrap a Mutex with an IDisposable class like this:

public class NamedMutex : IDisposable
{
    private static readonly object _syncLock = new object();
    private readonly Mutex _namedMutex;
    private readonly bool _createdNew;

    public NamedMutex(string name)
    {
        if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
        //lock (_syncLock)
        {
            _namedMutex = new Mutex(initiallyOwned: false, name: name, createdNew: out _createdNew);
        }
        _namedMutex.WaitOne();
    }

    public void Dispose()
    {
        //lock (_syncLock)
        {
            //if (_createdNew)
            _namedMutex.ReleaseMutex();
            _namedMutex.Dispose();
        }
    }
}

as you can see from the commented out code I've tried pretty much everything I could think of to make it work but either is my test wrong or something's not right with the above implementation because the test either never ends (probably a dead-lock that I am not able to identify or it crashes with the unsynchronized exception).

This is my test that I adapted for LINQPad:

void Main()
{
    var sw = Stopwatch.StartNew();

    var task1 = Task.Run(async () =>
    {
        using (new NamedMutex("foo"))
        {
            Console.WriteLine(3);
            await Task.Delay(TimeSpan.FromSeconds(3));
        }
    });

    var task2 = Task.Run(async () =>
    {
        using (new NamedMutex("foo"))
        {
            Console.WriteLine(2);
            await Task.Delay(TimeSpan.FromSeconds(2));
        }
    });

    Task.WaitAll(task1, task2);

    //Assert.IsTrue(sw.Elapsed.TotalSeconds >= 5);
    sw.Elapsed.Dump(); // LINQPad
}
t3chb0t
  • 16,340
  • 13
  • 78
  • 118
  • Can you put a breakpoint in the constructor of ```NamedMutex``` and tell us if it ever gets past the ```_namedMutex.WaitOne();``` line? – Matt Thomas Mar 09 '17 at 15:17
  • @MattThomas yes it does. If I add `Console.WriteLine("WaitOne");` below it then it prints `WaitOne` only once, then immediately `3` and it hangs like that forever. – t3chb0t Mar 09 '17 at 15:20
  • I just tested this in visual studio, and it works correctly the first time, but all subsequent attempts fail. Then if you change the name of the mutex, it works again once, then fails on subsequent calls again. It seems the mutex is not being properly disposed after program end. – Brandon Kramer Mar 09 '17 at 15:21
  • Also, if you wait long enough on the subsequent calls, `Task.WaitAll` throws an `AggregateException` containing an `ApplicationException` that says "Object synchronization method was called from an unsynchronized block of code." – Brandon Kramer Mar 09 '17 at 15:22
  • I wonder if it's related to the ```async``` and ```await```... [documentation](https://msdn.microsoft.com/en-us/library/system.threading.mutex.releasemutex(v=vs.110).aspx) talks about releasing it on the same thread as the one that acquired it, and I think ```await```ing something can cause code that comes afterward to execute on a different thread? Edit: Doh! Evk beat me to it – Matt Thomas Mar 09 '17 at 15:25

2 Answers2

5

This happens because of await. After your await Task.Delay(..) you might no longer be on the same thread you were before await statement. So in some cases you are trying to release your mutex from the thread which does not own it - hence your problem. That's easy to verify by writing current thread before and after await:

class Program {
    public static void Main() {
        while (true) {
            var sw = Stopwatch.StartNew();

            var task1 = Task.Run(async () => {                    
                using (new NamedMutex("foo")) {
                    Console.WriteLine("first before await: " + Thread.CurrentThread.ManagedThreadId);
                    await Task.Delay(TimeSpan.FromSeconds(2));
                    Console.WriteLine("first after await: " + Thread.CurrentThread.ManagedThreadId);
                }
            });

            var task2 = Task.Run(async () => {                    
                using (new NamedMutex("foo")) {
                    Console.WriteLine("second before await: " + Thread.CurrentThread.ManagedThreadId);
                    await Task.Delay(TimeSpan.FromSeconds(1));
                    Console.WriteLine("second after await: " + Thread.CurrentThread.ManagedThreadId);
                }
            });

            Task.WaitAll(task1, task2);

            //Assert.IsTrue(sw.Elapsed.TotalSeconds >= 5);
            Console.WriteLine(sw.Elapsed);
        }            
    }
}
Evk
  • 98,527
  • 8
  • 141
  • 191
  • I just realized this, then came to answer and you beat me to it. – Brandon Kramer Mar 09 '17 at 15:29
  • Oh, so this probably means I can forget about using a `Mutex` with `async/await`? – t3chb0t Mar 09 '17 at 15:30
  • Also, note that this is dependent on the current `SynchronizationContext`, in a UI app, such as WinForms or WPF this problem would not occur since their context will marshal the continuations to the UI thread, but console app doesn't have a "main" thread to marshal to, so the continuation just gets run on an arbitrary ThreadPool thread. – Brandon Kramer Mar 09 '17 at 15:30
  • @t3chb0t kind of yes. You might have noticed that you cannot do async\await inside "lock" statement, and now you can see why - because that's quite a bad idea to do that. – Evk Mar 09 '17 at 15:32
  • @t3chb0t There is a reason that lock statements don't allow async content. – Brandon Kramer Mar 09 '17 at 15:32
  • @BrandonKramer yeah, sorry about that :) – Evk Mar 09 '17 at 15:33
  • I've just searched for `Mutex` and `async` and found this: http://stackoverflow.com/questions/23153155/named-mutex-with-await so I'll change the test and will be careful not to use async in it's context. – t3chb0t Mar 09 '17 at 15:34
  • 1
    @t3chb0t If you need some sort of synchronization in async, you can use a `SemaphoreSlim`, which has a WaitAsync method. There is a blog about it [here](https://blog.cdemi.io/async-waiting-inside-c-sharp-locks/) – Brandon Kramer Mar 09 '17 at 15:38
  • @BrandonKramer I'll keep this in mind for future solutions however in this case I need a named mutex because the name will be a booking or a ticket number that two parallel processes can change but they should not both change them at the same time. They do different things based on data comming from two different sources. – t3chb0t Mar 09 '17 at 15:43
1

To expand on Evk's answer, and to get to a workaround, it is still possible to wrap a Mutex with an IDisposable. You just have to make sure that you have complete control over the Thread that is acquiring the Mutex and releasing it, and you have to make sure that the context doesn't switch in that thread between acquiring and releasing the mutex.

So just spin up your own thread. Something like:

class NamedMutex : IDisposable
{
    private readonly Thread _thread;
    private readonly ManualResetEventSlim _disposalGate;
    private readonly Mutex _namedMutex;
    public NamedMutex(string name)
    {
        var constructorGate = new ManualResetEventSlim();
        _disposalGate = new ManualResetEventSlim();
        _thread = new Thread(() =>
        {
            // Code here to acquire the mutex
            _namedMutex = new Mutex(initiallyOwned: false, name: name, createdNew: out _createdNew);

            constructorGate.Set(); // Tell the constructor it can go on
            _disposalGate.Wait(); // Wait for .Dispose to be called

            // Code here to release the mutex
            _namedMutex.ReleaseMutex();
            _namedMutex.Dispose();
        });
        _thread.Start();
        constructorGate.Wait();
    }

    public void Dispose()
    {
        _disposalGate.Set();
    }
}
Community
  • 1
  • 1
Matt Thomas
  • 5,279
  • 4
  • 27
  • 59
  • Please refer to my [comment](http://stackoverflow.com/questions/42698844/wrapping-a-mutex-with-idisposable-and-testing-it-but-the-test-never-ends#comment72521003_42699138). I need the name because it identifies the resource that currently two processes cannot modify at the same time. – t3chb0t Mar 09 '17 at 15:45
  • @t3chb0t Right. This is just the gist. So toss the name in through the ```NamedMutex``` constructor and acquire the named mutex where the ```// Code here to acquire the mutex``` comment is. I edited it to add those things – Matt Thomas Mar 09 '17 at 15:46
  • This seems like it would work, though it does involve creating a new thread each time, which is not cheap. – Brandon Kramer Mar 09 '17 at 15:48
  • @t3chb0t Yes, not cheap, unfortunately – Matt Thomas Mar 09 '17 at 15:49
  • 1
    Well maybe you are right, not completely sure about that but don't have any arguments left :) – Evk Mar 09 '17 at 16:01
  • I think there is a problem here. You are supposing we acquire the mutex inside the using statement, but if we don't succeed in doing that (e.g. using WaitOne with a timeout), we might reach the ReleaseMutex statement (in the constructor) without owning the mutex. And that is disallowed: https://learn.microsoft.com/en-us/dotnet/api/system.threading.mutex.releasemutex?view=netcore-3.0 "If the attempt to get ownership of the mutex fails ..., the thread shouldn't call ReleaseMutex" – Göran Roseen Oct 09 '19 at 15:51