9

Sto giocando con un servizio asincrono per un progetto Windows 8 e ci sono alcune chiamate asincrone di questo servizio, che dovrebbero essere chiamate una sola volta alla volta.Un'attività può avere più attenditori?

public async Task CallThisOnlyOnce() 
{ 
     PropagateSomeEvents(); 

     await SomeOtherMethod(); 

     PropagateDifferentEvents(); 
} 

Dal momento che non si può incapsulare una chiamata asincrona in una dichiarazione di blocco, ho pensato di utilizzare il modello AsyncLock, ma di quanto ho pensato che potrei anche provare qualcosa di simile:

private Task _callThisOnlyOnce; 
public Task CallThisOnlyOnce() 
{ 
     if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted) 
     _callThisOnlyOnce = null; 

     if(_callThisOnlyOnce == null) 
     _callThisOnlyOnce = CallThisOnlyOnceAsync(); 

     return _callThisOnlyOnce; 
} 

private async Task CallThisOnlyOnceAsync() 
{ 
     PropagateSomeEvents(); 

     await SomeOtherMethod(); 

     PropagateDifferentEvents(); 
} 

Pertanto si avrebbe finire con la chiamata CallThisOnlyOnceAsync eseguita solo una volta simultaneamente, e più attendenti collegati alla stessa attività.

È questo un modo "valido" di fare questo o ci sono alcuni inconvenienti a questo approccio?

risposta

6

Un'attività può avere più attenditori. Tuttavia, come ha sottolineato Damien, ci sono serie condizioni di gara con il codice proposto.

Se si desidera che il codice eseguito ogni volta che il metodo viene chiamato (ma non contemporaneamente), quindi utilizzare AsyncLock. Se si desidera che il codice venga eseguito una sola volta, utilizzare AsyncLazy.

La soluzione proposta tenta di combinare più chiamate, eseguendo di nuovo il codice se non è già in esecuzione. Questo è più complicato e la soluzione dipende in gran parte dalla semantica esatta di cui hai bisogno. Ecco una sola opzione:

private AsyncLock mutex = new AsyncLock(); 
private Task executing; 

public async Task CallThisOnlyOnceAsync() 
{ 
    Task action = null; 
    using (await mutex.LockAsync()) 
    { 
    if (executing == null) 
     executing = DoCallThisOnlyOnceAsync(); 
    action = executing; 
    } 

    await action; 
} 

private async Task DoCallThisOnlyOnceAsync() 
{ 
    PropagateSomeEvents(); 

    await SomeOtherMethod(); 

    PropagateDifferentEvents(); 

    using (await mutex.LockAsync()) 
    { 
    executing = null; 
    } 
} 

E 'anche possibile fare questo con Interlocked, ma che il codice diventa brutto.

P.S. Ho AsyncLock, AsyncLazy e altre async -prime primitive nel mio AsyncEx library.

+0

Mi sono piaciute entrambe le risposte, ma da quando hai aggiunto una proposta di implementazione, ho scelto la tua. Inoltre, ho provato ad installare la tua libreria usando Nuget, ma non sono riuscito nel mio progetto Windows Store (impossibile risolvere Microsoft.Bcl.Async) – UrbanEsc

+1

Prova a spuntare la casella di controllo "include prerelease". Il mio pacchetto è prerelease (ma NuGet non lo rileva correttamente), e 'Microsoft.Bcl.Async' è anche prerelease (che NuGet rileva correttamente). –

4

Questo codice sembra molto "vivace" se possono essere coinvolti più thread.

Un esempio (sono sicuro che ce ne sono altri). Si supponga che _callThisOnlyOnce è attualmente null:

Thread 1               Thread 2 

public Task CallThisOnlyOnce() 
{ 
    if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted) 
    _callThisOnlyOnce = null; 

    if(_callThisOnlyOnce == null) 
                    public Task CallThisOnlyOnce() 
                    { 
                    if(_callThisOnlyOnce != null && _callThisOnlyOnce.IsCompleted) 
                     _callThisOnlyOnce = null; 

                    if(_callThisOnlyOnce == null) 
                     _callThisOnlyOnce = CallThisOnlyOnceAsync(); 

                    return _callThisOnlyOnce; 
                    } 
    _callThisOnlyOnce = CallThisOnlyOnceAsync(); 

    return _callThisOnlyOnce; 
} 

Si dispone ora di 2 chiamate in esecuzione contemporaneamente.

Per quanto riguarda i vari attendenti, sì, è possibile farlo. Sono sicuro di aver visto un codice di esempio da MS da qualche parte che mostra un'ottimizzazione dove ad es. il risultato di Task.FromResult(0) viene archiviato in un membro statico e restituito ogni volta che la funzione desidera restituire zero.

Tuttavia, non sono riuscito a individuare questo esempio di codice.

Problemi correlati