2014-04-18 19 views
10

Quindi non è possibile utilizzare i blocchi thread-affine con async - come posso proteggere le risorse quando si eseguono più processi?Named Mutex con attesa

Per esempio ho due processi che utilizzano una Task qui sotto:

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

Se il moethod custodito da un mutex è sincrona quindi sopra il codice funzionerà ma con async mi metterò:

Il metodo di sincronizzazione oggetto è stato chiamato da un blocco di codice non sincronizzato.

Quindi è denominato Mutex inutile con codice asincrono?

+2

Molto correlato: http://stackoverflow.com/a/23122566/1768303 – Noseratio

+2

@Romasz: È possibile un'architettura diversa?Sto pensando ad un processo dedicato al proprietario di risorse che esegue richieste per conto di altri processi. –

+1

@Noseratio Grazie per il link, mi ha aiutato a capire alcune cose e ha rovinato la mia idea Semaphore;) – Romasz

risposta

9

È necessario assicurarsi che l'accesso a mutex sia costante su un determinato thread. Si potrebbe fare che in un certo numero di modi:

  1. Non utilizzare aspettano nel tratto critico durante il quale si tiene il mutex
  2. invocare il mutex invita un TaskScheduler che ha solo un unico filo

questo potrebbe assomigliare a questo:

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

Oppure, si utilizza il codice sincrono e spostare tutto al thread-pool. Se si ha accesso solo a una versione asincrona di DoSomething, si consideri solo chiamando Task.Wait sul suo risultato. Qui subirai una minore inefficienza. Probabilmente va bene.

+0

Devo pensare alla tua risposta e provare alcune cose. I problemi sono: Devo invocare l'attesa tenendo premuto il Mutex (è una sezione citica che dovrebbe essere sorvegliata - ad esempio l'accesso a un file) e non ho accesso a "DoSomething". Ho pensato che quando invoco 'attendi DoSomething', con default' ConfigureAwait (true) 'il contesto viene catturato e dopo aver terminato Task il resto viene continuato su quel contesto catturato. – Romasz

+2

Vero, ma il contesto catturato potrebbe significare "qualsiasi thread thread-pool". Viste le informazioni che ho, penso che tutte e 3 le soluzioni proposte funzionerebbero per te. Fammi sapere se hai bisogno di ulteriori informazioni. – usr

+0

Così come ora capisco quando torniamo dall'attesa arriviamo al contesto precedente, ma potremmo trovarci in una discussione diversa. Ho fatto alcuni studi sull'argomento: per quanto riguarda il tuo primo metodo - provo ad aprire un flusso con attendi, e voglio gurad quell'apertura, quindi deve essere fatto (IMO) mentre si tiene Mutex. Con la seconda proposta è diverso problema - [all'interno di RT] (http://www.jaylee.org/post/2012/03/17/No-Threads-for-you-in-metro-style-apps.aspx) è difficile limitare TaskScheduler a un thread: ho pensato a AutoResteEvent = ma non sono sicuro che garantirà un thread ... – Romasz

-1

Questa protezione funziona con async/attendono perfettamente:

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

Questo è in attesa sincrona di rimuovere il blocco, non in attesa asincrona per estrarre il blocco. – Servy

+0

2 Servire: leggere l'attività, per favore. Non si tratta di attesa asincrona. Se ne hai bisogno, chiedimi, te lo darò. –

+0

È un metodo asincrono; ovviamente dovrebbe fare l'attesa in modo asincrono. Il metodo cesserebbe di essere asincrono se fosse in attesa sincrona di togliere il lucchetto. – Servy

1

Ho una soluzione interessante per voi. Non ho tempo di fornire un esempio di codice adesso, quindi se la mia descrizione non è sufficiente fammelo sapere e proverò a fornire il codice.

Hai due problemi qui. Innanzitutto, un AsyncMutex non ha affinità di thread, come hai sottolineato. Quindi non puoi costruirne uno da un Mutex. Puoi, tuttavia, costruirne uno da un semaforo con un conteggio pari a 1, in quanto un semaforo non ha affinità di thread. In C# la classe Semaphore può essere denominata e utilizzata oltre i limiti del processo. Quindi il primo problema è abbastanza facile da risolvere.

Il secondo problema consiste nel non voler utilizzare il blocco delle chiamate quando si "blocca" questo AsyncMutex. Bene, puoi usare ThreadPool.RegisterWaitForSingleObject per registrare un callback da eseguire quando viene segnalato il semaforo (un WaitHandle). Questo fa "attesa asincrona". Avvolgilo con un po 'di codice usando TaskCompletionSource e puoi creare un Task che restituisca il metodo WaitAsync su AsyncMutex abbastanza facilmente. Queste due idee dovrebbero rendere abbastanza facile implementare un processo incrociato denominato AsyncMutex utilizzabile in C#.

Ricorda che, come altre implementazioni di AsyncMutex, questo non sarà un mutex ricorsivo (lo stesso "thread" può bloccare il mutex più volte fintanto che sblocca il mutex lo stesso numero di volte) , quindi è necessario prestare attenzione nel codice per non causare stallo.

+0

Hai ragione che * Semaphore * può essere usato per quello scopo. Tuttavia, per il mio semplice scopo e il breve processo, è stato più facile eseguirlo in modo sincrono su un singolo thread. – Romasz