Именованный Mutex с ожиданием

Следовательно, я не могу использовать поточно-аффинные блокировки с async - как я могу защитить свои ресурсы при запуске нескольких процессов?

Например, у меня есть два процесса, которые используют задачу ниже:

 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(); }
     }
 }

Если moethod, защищенный Mutex, синхронно, то выше код будет работать, но с async я получу:

Метод синхронизации объектов вызывается из несинхронизированного блока кода.

Итак, Именованный Mutex бесполезен с асинхронным кодом?

Ответ 1

Вы должны обеспечить постоянный доступ к мьютексу в определенном потоке. Вы можете сделать это несколькими способами:

  • Не используйте ожидание в критическом разделе, в течение которого вы сохраняете мьютекс
  • Вызов мьютексов на TaskScheduler, который имеет только один поток

Это может выглядеть так:

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

Или вы используете синхронный код и переместите все в пул потоков. Если у вас есть доступ к асинхронной версии DoSomething, рассмотрите только вызов Task.Wait в его результате. Здесь вы будете испытывать незначительную неэффективность. Наверное, отлично.

Ответ 2

У меня есть интересное решение для вас. У меня нет времени, чтобы предоставить образец кода прямо сейчас, поэтому, если мое описание недостаточно, дайте мне знать, и я попробую предоставить код.

У вас здесь две проблемы. Во-первых, AsyncMutex не имеет сходства потоков, как вы указали. Таким образом, вы не можете создать один из Mutex. Вы можете, однако, построить один из Семафора со счетом 1, так как семафор также не имеет сходства нитей. В С# класс Семафора может быть назван и использоваться через границы процесса. Поэтому первая проблема довольно легко решена.

Вторая проблема заключается в том, что вы не хотите использовать блокирующие вызовы, когда вы "блокируете" этот AsyncMutex. Ну, вы можете использовать ThreadPool.RegisterWaitForSingleObject для регистрации обратного вызова, который должен выполняться, когда сигнализируется Семафор (WaitHandle). Это делает "асинхронное ожидание". Оберните это небольшим количеством кода с помощью TaskCompletionSource, и вы можете легко создать задачу, возвращающую метод WaitAsync на вашем AsyncMutex. Эти две идеи должны упростить реализацию кросс-процесса с именем AsyncMutex, используемого в С#.

Имейте в виду, что, как и другие реализации AsyncMutex, вы обнаружите, что это не будет рекурсивный мьютекс (тот же "поток" может блокировать мьютексы несколько раз, пока он разблокирует мьютексы одинаковое количество раз), поэтому в коде необходимо соблюдать осторожность, чтобы не вызвать тупик.

Ответ 3

Я использую именованный Mutex в асинхронном методе, чтобы контролировать, что только один процесс вызывает его. Другие проверки процесса для имени Mutex и выходит, если он не может создать новый именованный мьютекс.

Я могу использовать именованный Mutex в асинхронном методе, поскольку ОС гарантирует/контролирует только один экземпляр именованного объекта в ОС. Кроме того, я не использую WaitOne/Release, который должен вызываться в потоке.

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.
        }
    }
}

Ответ 4

Вы можете использовать двоичный семафор вместо мьютекса. Семафор не должен выпускаться тем же потоком, который его получил. Большим недостатком здесь является то, что если приложение выходит из строя или уничтожается в DoSomething(), семафор не будет освобожден, и следующий экземпляр приложения будет зависать. См. Заброшенный названный семафор не выпущен

 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(); }
     }
 }

Ответ 5

Этот защитник работает с асинхронным/ожиданием:

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
}