Named Mutex with await

后端 未结 5 2128
感动是毒
感动是毒 2020-11-30 09:56

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 u

相关标签:
5条回答
  • 2020-11-30 10:37

    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.

    0 讨论(0)
  • 2020-11-30 10:38

    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
    }
    
    0 讨论(0)
  • 2020-11-30 10:44

    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.

    0 讨论(0)
  • 2020-11-30 10:53

    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.
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 10:53

    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(); }
         }
     }
    
    0 讨论(0)
提交回复
热议问题