2015-01-15 10 views
6

Sto modificando una libreria per aggiungere metodi asincroni. Da Should I expose synchronous wrappers for asynchronous methods? si afferma che non dovrei semplicemente scrivere un wrapper attorno a Task.Result quando si chiama il metodo sincrono. Ma come faccio a non dover duplicare un sacco di codice tra metodi asincroni e metodi di sincronizzazione, dato che vogliamo mantenere entrambe le opzioni nella libreria?Pattern per scrivere i metodi sincroni e asincroni nelle librerie e tenerlo DRY

Ad esempio, la libreria utilizza attualmente il metodo TextReader.Read. Parte del cambiamento asincrono vorremmo usare il metodo TextReader.ReadAsync. Poiché questo è al centro delle librerie, sembrerebbe che avrei bisogno di duplicare un sacco di codice tra i metodi sincroni e asincroni (voglio mantenere il codice DRY il più possibile). O ho bisogno di rifattorizzarli in un PreRead e PostRead metodi che sembrano ingombrare il codice e ciò che il TPL stava cercando di risolvere.

Sto pensando di avvolgere il metodo TextReader.Read in un Task.Return(). Anche se si tratta di un compito, i miglioramenti del TPL non dovrebbero farlo passare a un thread diverso e posso ancora usare async per attendere la maggior parte del codice come normale. Sarebbe quindi ok se un wrapper del sincrono fosse solo Task.Result o Wait()?

Ho esaminato altri esempi nella libreria .net. Lo StreamReader sembra duplicare il codice tra asincrono e non asincrono. Il MemoryStream fa un Task.FromResult.

Anche pianificando ovunque potrei aggiungere ConfigureAwait(false) in quanto è solo una libreria.

Aggiornamento:

di cosa sto parlando di codice duplicato è

public decimal ReadDecimal() 
{ 
    do 
    { 
      if (!Read()) 
      { 
       SetInternalProperies() 
      } 
      else 
      { 
       return _reader.AsDecimal(); 
      } 
     } while (_reader.hasValue) 
} 

public async Task<decimal> ReadDecimalAsync() 
{ 
    do 
    { 
      if (!await ReadAsync()) 
      { 
       SetInternalProperies() 
      } 
      else 
      { 
       return _reader.AsDecimal(); 
      } 
     } while (_reader.hasValue) 
    } 

Questo è un piccolo esempio, ma si può vedere l'unico cambiamento di codice è la attesa e il compito.

Per essere chiari, desidero codificare utilizzando async/await e TPL ovunque nella libreria, ma ho ancora bisogno di avere anche i vecchi metodi di sincronizzazione. Non sto solo per il Task.FromResult() metodi di sincronizzazione. Quello che stavo pensando stava avendo una bandiera che dice io voglio il metodo di sincronizzazione e alla radice controllare il flag qualcosa come

public decimal ReadDecimal() 
{ 
    return ReadDecimalAsyncInternal(true).Result; 
} 

public async Task<decimal> ReadDecimal() 
{ 
    return await ReadDecimalAsyncInternal(false); 
} 

private async Task<decimal> ReadDecimalAsyncInternal(bool syncRequest) 
{ 
    do 
    { 
      if (!await ReadAsync(syncRequest)) 
      { 
       SetInternalProperies() 
      } 
      else 
      { 
       return _reader.AsDecimal(); 
      } 
     } while (_reader.hasValue) 
} 

private Task<bool> ReadAsync(bool syncRequest) 
{ 
    if(syncRequest) 
    { 
     return Task.FromResult(streamReader.Read()) 
    } 
    else 
    { 
     return StreamReader.ReadAsync(); 
    } 
} 
+2

Perché si desidera esporre la versione sincrona e asincrona del metodo? Un'operazione può essere sincrona o asincrona. Non può essere entrambi. Scegli la cosa giusta, non entrambe. Quindi con il codice cliente, puoi chiamarlo comunque. –

+0

È una biblioteca. Abbiamo codice esistente che lo usa e non vogliamo dover cambiare tutto questo. È la stessa cosa di TextReader.Read e TextReader.ReadAsync stiamo aggiungendo il metodo "asincrono" alla libreria come Library.Method e Library.MethodAsync – CharlesNRice

+1

Un wrapper Task.Result non fornisce nulla di positivo. attendere attiverà semplicemente in modo sincrono il valore memorizzato in quell'attività. La firma del metodo è fuorviante. Esponi solo i metodi asincroni che sono in realtà asincroni internamente. – usr

risposta

1

Sarebbe poi essere ok per avere un wrapper del sincrono essere solo compito. Risultato o Attesa()?

Devi capire che cosa è l'IO asincrono. Non riguarda la duplicazione del codice, ma sfrutta il fatto che non è necessario alcun thread quando il lavoro è naturalmente asincrono.

Se si esegue il wrapping del codice sincrono con un'attività, si perde questo vantaggio. Inoltre, potresti ingannare i tuoi chiamanti API quando supponevano che la chiamata atteso restituisse il controllo al chiamante.

Edit:

I suoi esempi rafforza il mio punto. Non dall'uso di compiti. Gli apis sincroni da soli sono completamente a posto, non forzare l'uso di TPL in essi quando non è necessario, eveb se fa sì che il codice base cresca di 2x la quantità di linee.

Prenditi del tempo per implementare correttamente la tua async asso. Non bloccare il codice asincrono, tenerlo scorrere fino in fondo alla pila.

+0

Voglio fare l'opposto. Voglio scrivere usando il codice atteso asincrono TPL ma non posso rompere la libreria esistente che è attualmente scritta usando il codice di sincronizzazione. L'esempio è che voglio usare TextReader.ReadAsync dove attualmente TextReader.Read. Farò in modo che tutte le chiamate al metodo abbiano l'attività <> attendere le chiamate. Ma ho ancora bisogno di supportare i vecchi metodi di sincronizzazione e che volevo usare il codice Async e solo bloccare su Result o Wait(). Ma a quanto sembra disapprovato. – CharlesNRice

+2

@CharlesNRice si desidera aggiungere metodi asincroni oltre a quelli sincroni nella libreria, giusto?L'articolo che hai collegato parla esattamente di questo. Non aggiungere asincroni sui wrapper di sincronizzazione o viceversa. Perché l'articolo non risponde alla tua domanda? – usr

+0

Non bloccare i risultati. Questa è una fonte di molti errori e deadlock. Jeep sincrono la versione sincrona e scrivere l'API asincrona usando * async completamente *, dall'alto verso il basso. –

4

Si desidera aggiungere metodi asincroni oltre a quelli sincroni nella libreria. L'articolo che hai collegato parla esattamente di questo. Si consiglia di creare codice specializzato per entrambe le versioni.

Ora che consiglio è di solito dato perché:

  1. metodi asincroni dovrebbero essere a bassa latenza. Per efficienza dovrebbero usare internamente un IO asincrono.
  2. I metodi di sincronizzazione devono utilizzare l'IO di sincronizzazione internamente per motivi di efficienza.

Se si creano wrapper si potrebbero indurre in errore i chiamanti.

Ora, è una strategia valida per creare wrapper in entrambe le direzioni se si è soddisfatti delle conseguenze. Sicuramente salva molto codice. Ma dovrai decidere se dare la preferenza alla sincronizzazione o alla versione asincrona. L'altro sarà meno efficiente e non ci sarà alcun motivo basato sulle prestazioni.

Raramente lo si trova nel BCL perché la qualità dell'implementazione è elevata. Ad esempio, la classe SqlConnection di ADO.NET 4.5 utilizza sync-over-async. Il costo di effettuare una chiamata SQL è di gran lunga maggiore rispetto al sovraccarico di sincronizzazione. Questo è un caso d'uso OK. MemoryStream utilizza (tipo di) sincronizzazione sincrona asincrona perché è inerente solo alla CPU, ma deve implementare Stream.

Qual è il sovraccarico in realtà? Aspettatevi di essere in grado di eseguire> 100 milioni Task.FromResult al secondo e milioni di quasi zero lavoro Task.Run al secondo. Questo è un piccolo overhead rispetto a molte cose.

Problemi correlati