Named Mutex with await

2019-01-14 17: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 moethod 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?

3条回答
时光不老,我们不散
2楼-- · 2019-01-14 17:39

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.

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-01-14 17:48

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.

查看更多
走好不送
4楼-- · 2019-01-14 17:58

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
}
查看更多
登录 后发表回答