26

Hence I can't use thread-affine locks with async - how can I guard my resources when running multiple processes?

For example I've two processes that use a Task below:

 public async Task<bool> MutexWithAsync()
 {
     using (Mutex myMutex = new Mutex(false, "My mutex Name"))
     {
         try
         {
             myMutex.WaitOne();
             await DoSomething();
             return true;
         }
         catch { return false; }
         finally { myMutex.ReleaseMutex(); }
     }
 }

If the method guarded by a Mutex is synchronous then above code will work but with async I will get:

Object synchronization method was called from an unsynchronized block of code.

So is Named Mutex useless with asynchronous code?

Bondolin
  • 2,793
  • 7
  • 34
  • 62
Romasz
  • 29,662
  • 13
  • 79
  • 154
  • 3
    Highly related: http://stackoverflow.com/a/23122566/1768303 – noseratio Apr 18 '14 at 12:15
  • 2
    @Romasz: Is a different architecture a possibility? I'm thinking a dedicated resource-owner process that executes requests on behalf of other processes. – Stephen Cleary Apr 18 '14 at 14:09
  • 1
    @Noseratio Thank you for the link, it helped me to understand some things and ruined my Semaphore idea ;) – Romasz Apr 18 '14 at 15:22
  • @StephenCleary It could be a solution, but currenlty I'm working on WP and it has limited possibility of background tasks. Probably I could fake an agent for that purpose, but I would like to avoid that. Thank you for help, your sentence just helped me to get another idea. Nevertheless I will try to find out more about Mutex and async. – Romasz Apr 18 '14 at 15:29

5 Answers5

16

You must ensure that mutex is being accessed consistently on a certain thread. You could do that in a number of ways:

  1. Do not use await in the critical section during which you hold the mutex
  2. Invoke the mutex calls on a TaskScheduler that only has a single thread

That could look like this:

await Task.Factory.StartNew(() => mutex.WaitOne(), myCustomTaskScheduler);

Or, you use synchronous code and move everything to the thread-pool. If you only have access to an async version of DoSomething, consider just calling Task.Wait on its result. You'll suffer a minor inefficiency here. Probably fine.

usr
  • 168,620
  • 35
  • 240
  • 369
  • I must think about your answer and try some things. THe problems are: I must invoke await while holding Mutex (it's a citical section which should be guarded - for example accessing a file) and I don't have access to `DoSomething`. I thought that when I invoke `await DoSomething` then with default `ConfigureAwait(true)` the context is being captured and after finishing Task the rest is continued on that captured context. – Romasz Apr 18 '14 at 12:10
  • 2
    True, but the captured context might well mean "any thread-pool thread at all". Given the information that I have I think all 3 proposed solutions would work for you. Let me know if you require additional information. – usr Apr 18 '14 at 12:17
  • So as I now understand when we are coming back from await we are coming to previous context, but we may be in a different thread. I've made some studies over the subject: As for your first method - I try to open a stream with await, and I want to gurad that opening, so it must be done (IMO) while holding Mutex. With second proposal is different problem - [within RT](http://www.jaylee.org/post/2012/03/17/No-Threads-for-you-in-metro-style-apps.aspx) it is hard to limit TaskScheduler to one thread - I thought about AutoResteEvent = but I'm not sure if it will guarantee one thread ... – Romasz Apr 18 '14 at 18:21
  • As for third option - I've implemented it, seems to work. As I understand it makes my code litlle more synchronus, but opening a stream doesn't take so much time and thus as you have said it's a minor issue. – Romasz Apr 18 '14 at 18:23
  • @Romasz regarding (2): You can achieve the same effect by using a task instead of a thread. You'd need to write a TaskScheduler that is backed by a thread-pool task (with the LongRunning option set). regarding (3): You can just wrap all of the synchronous waiting with `Task.Run` to move it off the UI thread. This way you stay 100% responsive. The only disadvantage is that you burn the memory of one thread-pool thread, which is not too bad. – usr Apr 18 '14 at 18:26
  • As for (2) - I'll have to study this more, but probably after I finish current job. As for (3) - If I'm not wrong I cannot so simply just wrap synchronous waiting with `Task.Run` - I'm opening the stream so I have to wait for it - if it is synchronous it blocks me, but I stay within one thread, if I wrap it with Run, I would have to do await (which I don't want to -> Mutex and other thread when ruturned). Or maybe I don't understand something. – Romasz Apr 18 '14 at 18:45
8

I use named Mutex in async method to control that only one process call it. Another process checks for named Mutex and exits if it cannot create new named Mutex.

I can use named Mutex in async method as OS guarantees/controls only one instance of named object in OS. Plus, I do not use WaitOne/Release that should be called on the thread.

public async Task<bool> MutexWithAsync()
{
    // Create OS-wide named object. (It will not use WaitOne/Release)
    using (Mutex myMutex = new Mutex(false, "My mutex Name", out var owned))
    {
        if (owned)
        {
            // New named-object was created. We own it.
            try
            {
                await DoSomething();
                return true;
            }
            catch
            {
                return false;
            }
        }
        else
        {
            // The mutex was created by another process.
            // Exit this instance of process.
        }
    }
}
Sergei Zinovyev
  • 1,238
  • 14
  • 14
  • The problem is that `await DoSomething();` can continue on different thread if default synchronization context was used. Have you tried this on non-UI thread? – Romasz Jan 22 '19 at 13:33
  • Yes, it can continue on different thread. My main idea that I do not synchronize threads of the same process. I create named kernel object. A named kernel object can be created only once, the second attempt of creating it will fail. In terms of C#: if named object is created the `owned` will be `true`. If creating named object failed, the `owned` will be `false`. And I can use/create any kernel named object for this purpose. https://learn.microsoft.com/en-us/windows/desktop/sysinfo/kernel-objects Event, Mutex and Semaphore are good. – Sergei Zinovyev Jan 22 '19 at 22:34
  • Now I see your point, nice idea. I'm just thinking if there is any chance for a race condition here, though can't find any. – Romasz Jan 23 '19 at 08:10
  • This is great! It looks like when the process exits the mutex is automatically released? – Felix Jul 13 '21 at 16:36
  • Felix, yes, as it is derived from a class that has `finalizer`. – Sergei Zinovyev Jul 14 '21 at 17:38
2

You can use a binary Semaphore instead of a Mutex. A Semaphore does not need to be release by the same thread that acquired it. The big disadvantage here is if the application crashes or is killed within DoSomething() the semaphore will not be released and the next instance of the app will hang. See Abandoned named semaphore not released

 public async Task<bool> MutexWithAsync()
 {
     using (Semaphore semaphore = new Semaphore(1, 1, "My semaphore Name"))
     {
         try
         {
             semaphore.WaitOne();
             await DoSomething();
             return true;
         }
         catch { return false; }
         finally { semaphore.Release(); }
     }
 }
innominate227
  • 11,109
  • 1
  • 17
  • 20
  • This is what [wekempf](https://stackoverflow.com/a/45338799/2681948) suggested in his answer. – Romasz Feb 06 '19 at 08:08
  • 1
    Unfortunately, named Semaphore don't seem to work on all OSes. ```System.PlatformNotSupportedException: The named version of this synchronization primitive is not supported on this platform. Stack Trace: at System.Threading.Semaphore.CreateSemaphoreCore(Int32 initialCount, Int32 maximumCount, String name, Boolean& createdNew) at System.Threading.Semaphore..ctor(Int32 initialCount, Int32 maximumCount, String name, Boolean& createdNew) at System.Threading.Semaphore..ctor(Int32 initialCount, Int32 maximumCount, String name)``` – Steve R. Aug 19 '22 at 20:18
1

I've got an interesting solution for you. Don't have time to provide a code sample right now, so if my description isn't enough let me know and I'll try and provide code.

You've got two problems here. First, an AsyncMutex doesn't have thread affinity, as you've pointed out. So you can't build one out of a Mutex. You can, however, build one out of a Semaphore with a count of 1, since a semaphore doesn't have thread affinity either. In C# the Semaphore class can be named and used across process boundaries. So the first issue is fairly easily solved.

The second problem is in not wanting to use blocking calls when you "lock" this AsyncMutex. Well, you can use ThreadPool.RegisterWaitForSingleObject to register a callback to be executed when the Semaphore (a WaitHandle) is signaled. This does an "asynchronous wait". Wrap that with a bit of code using a TaskCompletionSource and you can build a Task returning WaitAsync method on your AsyncMutex fairly easily. These two ideas should make it fairly easy to implement a cross process named AsyncMutex usable in C#.

Keep in mind that like other AsyncMutex implementations you'll find, this won't be a recursive mutex (the same "thread" can lock the mutex multiple times so long as it unlocks the mutex the same number of times), so care must be taken in code to not cause deadlock.

wekempf
  • 2,738
  • 15
  • 16
  • You are right that *Semaphore* can be used for that purpose. Nevertheless for my simple purpose and short process it was easier to make it run synchronously on single thread. – Romasz Jul 27 '17 at 13:49
  • You can also use SemaphoreSlim if you want something lighter and don't need the naming or cross process synchronization. – Lucas Martins Juviniano Jun 05 '18 at 03:34
-4

This guard works with async/await perfectly:

public sealed class AsyncLock : IDisposable
{
    readonly AutoResetEvent Value;

    public AsyncLock(AutoResetEvent value, int milliseconds = Timeout.Infinite)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        value.WaitOne(milliseconds);
        Value = value;
    }

    void IDisposable.Dispose()
    {
        Value.Set();
    }
}

private async Task TestProc()
{
    var guard = new AutoResetEvent(true); // Guard for resource

    using (new AsyncLock(guard)) // Lock resource
    {
        await ProcessNewClientOrders(); // Use resource
    } // Unlock resource
}
  • 1
    This is synchronously waiting to take out the lock, not asynchronously waiting to take out the lock. – Servy Apr 28 '15 at 14:55
  • 2 Servy: read the task, please. It is not about asynchronous waiting. If you need one — ask me, i`ll give you that. – Alexander Yudakov May 01 '15 at 10:22
  • It's an asynchronous method; of course it should be doing the waiting asynchronously. The method would cease to be asyncrhonous if it was synchronously waiting to take out the lock. – Servy May 01 '15 at 12:56