2014-11-12 5 views
14

ho un unico servizio consente di dire,Await o Task.FromResult

public interface ISomeService 
{ 
    Task<bool> DoSomeExpensiveCheckAsync(string parameter); 
} 

E devo questa classe di consumare il servizio. Ha solo bisogno di fare alcuni semplici controlli null e quindi restituire la risposta del servizio.

public class SomeServiceConsumer 
{ 
    private readonly ISomeService _serviceClient; 

    public SomeServiceConsumer(ISomeService serviceClient) 
    { 
     _serviceClient = serviceClient; 
    } 

    public async Task<bool> DoSomething1Async(string someParameter) 
    { 
     if (string.IsNullOrWhiteSpace(someParameter)) 
     { 
      return false; 
     } 
     return await _serviceClient.DoSomeExpensiveCheckAsync(someParameter); 
    } 

    //No async or await keywords 
    public Task<bool> DoSomething2Async(string someParameter) 
    { 
     if (string.IsNullOrWhiteSpace(someParameter)) 
     { 
      return Task.FromResult(false); 
     } 
     return _serviceClient.DoSomeExpensiveCheckAsync(someParameter); 
    } 
} 

Devo fare DoSomething1Async o DoSomething2Async?

Secondo this answer, non dovrei avvolgere con un inutile await ma poi ho dovuto usare Task.FromResult(false) per cortocircuitando come in DoSomething2Async

Ma secondo this answer ci sono casi con try/catch e using dichiarazioni in cui ho effettivamente dovrebbe await prima ritorno.

Ho ragione nel dire quindi, che

  1. Se devo usare try/catch o using allora dovrei await

  2. altrimenti non await se si sta solo andando a tornare. E utilizzare Task.FromResult per corto circuito

mi piace di più DoSomething1Async, e voglio farlo ovunque, se qualcuno dice che non importa :).

risposta

13

Se siete preoccupati a questo proposito, cache l'Task:

static readonly Task<bool> falseTask = Task.FromResult(false); 

Il async parola chiave avvolge anche delle eccezioni nella tornata Task, insieme ad una corretta analisi dello stack. È un compromesso, la sicurezza del comportamento per il perf.

consente di guardare gli scenari di differenza dove ogni sarebbe diverso:

async Task UseSomething1Async(string someParameter) 
{ 
    // if IsNullOrWhiteSpace throws an exception, it will be wrapped in 
    // the task and not thrown here. 
    Task t1 = DoSomething1Async(someParameter); 

    // rather, it'll get thrown here. this is best practice, 
    // it's what users of Task-returning methods expect. 
    await t1; 

    // if IsNullOrWhiteSpace throws an exception, it will 
    // be thrown here. users will not expect this. 
    Task t2 = DoSomething2Async(someParameter); 

    // this would never have been reached. 
    await t2; 
} 

Basta che illustra il punto qui - IsNullOrWhiteSpace in realtà non buttare eccezioni per nessun motivo.

Per quanto riguarda le tracce di stack, le tracce di stack asincrone sono determinate dal punto in cui è await. No await significa che il metodo sparirà dalla traccia dello stack.

Dire DoSomeExpensiveCheckAsync genera un'eccezione. Nel caso di DoSomething1Async, la traccia dello stack sarà simile a caller -> DoSomething1Async -> DoSomeExpensiveCheckAsync.

Nel caso di DoSomething2Async, la traccia di stack potrebbe apparire come caller -> DoSomeExpensiveCheckAsync. A seconda della complessità del tuo codice, ciò può rendere difficile il debug delle cose.

In pratica, in genere, restituirò un Task solo se non avessi previsto eccezioni prima di esso e se il nome del metodo fosse semplicemente un sovraccarico di inoltro a un altro sovraccarico.Ci sono sempre delle eccezioni a questa regola, ci sono luoghi in cui si desidera massimizzare le prestazioni. Scegli e scegli attentamente, rendi conto che potresti rendere più difficile la vita di te e del tuo utente.

+0

Grazie Cory. Non capisco come influisce sulla sicurezza del comportamento però. Vedi il mio commento alla risposta di @ I3arnon. – labroo

+0

Ho modificato in una spiegazione. –

4

Non ha importanza. Se sei a tuo agio con sempre contrassegnando i metodi Task -returning con la parola chiave async, procedi e utilizza DoSomething1.

Come hai detto, si tratta di un compromesso:

  • DoSomething2 non genera la macchina dello Stato necessario per un metodo async e quindi è un po ' più veloce (ma la differenza è in gran parte trascurabile).

  • D'altra parte si può avere alcuni effetti collaterali imprevisti per quanto riguarda la gestione delle eccezioni in quanto in un metodo async l'eccezione sarebbe memorizzato nella tornata Task e nell'altra sarebbe gettato regolarmente.

+0

Non capisco la differenza di eccezione @ I3arnon. se eseguo DoSomeExpensiveCheckAsync nel servizio genera un'eccezione esplicita, quindi "var from1 = attende someServiceCustomer.DoSomething1Async (" Hello ");" e " var from2 = attende someServiceCustomer.DoSomething2Async (" Hello ");" lanciare la stessa identica eccezione. DoSomething1Async ha però un TaskAwaiter.ThrowForNonSuccess aggiuntivo nello stacktrace. Ciò dimostra che DoSomething1Async sta facendo qualcosa in più, ma non ottengo la parte dell'effetto collaterale spiacevole. – labroo

+0

@labroo Per capire la differenza è necessario pensare a cosa succederebbe senza un 'await':' var task = DoSomethingAsync ("Hello") '. Se c'è un'eccezione e il metodo è asincrono, ciò non genera un'eccezione. L'eccezione verrebbe archiviata nell'attività e riattivata in attesa. Se il metodo non è asincrono, l'eccezione verrebbe generata come qualsiasi altro metodo. – i3arnon

+0

@labroo Ecco alcune ulteriori informazioni: http://stackoverflow.com/a/24441859/885318 – i3arnon