2015-06-15 8 views
6

Prendiamo ad esempio la seguente interfaccia:Come implementare il metodo di ritorno attività sincrono senza avviso CS1998?

interface IOracle 
{ 
    Task<string> GetAnswerAsync(string question); 
} 

Alcune implementazioni di questa interfaccia potrebbe utilizzare async/await. Altri potrebbero non aver bisogno di Ad esempio, considera questa semplice implementazione del giocattolo.

class SimpleOracle 
{ 
    public Dictionary<string, string> Lookup { get; set; } 

    // Warning CS1998: This async method lacks 'await' operators 
    // and will run synchonously. 
    public async Task<string> GetAnswerAsync(string question) 
    { 
     string answer = Lookup[question]; 
     return answer; 
    } 
} 

Il warning del compilatore CS1998 ha ovviamente senso. Il solito suggerimento è a remove the async keyword and use Task.FromResult, ma manca un sottile problema. Cosa succede se il codice genera un'eccezione? Quindi la trasformazione del codice modifica il comportamento del metodo: la versione async avvolgerà qualsiasi eccezione in un Task; la versione non async non eseguirà, senza una spiegazione try - catch.

Lasciando che la parola chiave async funzioni esattamente come voglio, ma genera un avviso del compilatore e non penso che la soppressione di quelli sia saggia.

Come devo rifattorizzare la mia implementazione del metodo per non produrre un avviso del compilatore e avvolgere tutte le eccezioni con Task come farebbe qualsiasi altro metodo async?

risposta

5

La traduzione meccanica che utilizzo per convertire dalla versione async che restituisce l'avviso del compilatore CS1998 a una versione non async che si comporta in modo identico è la seguente.

  • Rimuovere la parola chiave async.
  • Avvolgere l'intero corpo del metodo esistente con try - catch.
  • Definire un TaskCompletionSource<T> chiamato prima del try - catch.
  • Sostituire tutte le istanze esistenti di return <expr>; con tcs.SetResult(<expr>); seguito da return tcs.Task;.
  • Definire il blocco catch per chiamare tcs.SetException(e) seguito da return tcs.Task;.

Ad esempio:

public Task<string> GetAnswerAsync(string question) 
{ 
    var tcs = new TaskCompletionSource<string>(); 
    try 
    { 
     string answer = Lookup[question]; 
     tcs.SetResult(answer); 
     return tcs.Task; 
    } 
    catch (Exception e) 
    { 
     tcs.SetException(e); 
     return tcs.Task; 
    } 
} 

Questo può essere espresso più in generale con la seguente, anche se non so se sarebbe opportuno introdurre in realtà un tale metodo di supporto in una base di codice.

public static Task<T> AsyncPattern(Func<T> func) 
{ 
    var tcs = new TaskCompletionSource<T>(); 
    try 
    { 
     tcs.SetResult(func()); 
    } 
    catch (Exception e) 
    { 
     tcs.SetException(e); 
    } 
    return tcs.Task; 
} 
+0

Controllare [questo] (http://stackoverflow.com/a/30852187/1768303) e [questo] (http : // stackove rflow.com/a/21082631/1768303) per un'altra opzione: 'Task.RunSynchronously'.Personalmente mi piace più dell'approccio 'TaskCompletionSource'. – Noseratio

3

Se stai usando .NET 4.6, è possibile utilizzare Task.FromException per gestire il caso un'eccezione, proprio come si usa FromResult per gestire il caso di successo:

public Task<string> GetAnswerAsync(string question) 
{ 
    try 
    { 
     return Task.FromResult(Lookup[question]); 
    } 
    catch(Exception e) 
    { 
     return Task.FromException<string>(e); 
    } 
} 

Se si sta utilizzando. NET 4.5, allora avrete bisogno di scrivere il proprio metodo di FromException, ma è piuttosto banale:

public static Task<T> FromException<T>(Exception e) 
{ 
    var tcs = new TaskCompletionSource<T>(); 
    tcs.SetException(e); 
    return tcs.Task; 
} 
public static Task FromException(Exception e) 
{ 
    var tcs = new TaskCompletionSource<bool>(); 
    tcs.SetException(e); 
    return tcs.Task; 
}