Tomemos como ejemplo la siguiente interfaz:
interface IOracle { Task<string> GetAnswerAsync(string question); } Algunas implementaciones de esta interfaz pueden usar async / await . Es posible que otros no necesiten hacerlo. Por ejemplo, considere esta implementación de juguete simple.
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; } } La advertencia del compilador CS1998, por supuesto, tiene sentido. La sugerencia habitual es eliminar la palabra clave async y usar Task.FromResult , pero se pierde un problema sutil. ¿Qué sucede si el código arroja una excepción? Luego, esa transformación de código cambia el comportamiento del método: la versión async envolverá cualquier excepción en una Task ; la versión no async no lo hará, sin un try - catch explícito.
Dejar la palabra clave async funciona exactamente como quiero, pero genera una advertencia del compilador, y no creo que sea prudente suprimirla.
¿Cómo debo refactorizar la implementación de mi método para no producir una advertencia del compilador y al mismo tiempo envolver todas las excepciones con Task como lo haría cualquier otro método async ?
La traducción mecánica que uso para convertir de la versión async que produce la advertencia del compilador CS1998 a una versión no async que se comporta de manera idéntica es la siguiente.
async .try - catch .TaskCompletionSource<T> llamado tcs antes de try - catch .return <expr>; con tcs.SetResult(<expr>); seguido de return tcs.Task; .catch para llamar a tcs.SetException(e) seguido de return tcs.Task; .Por ejemplo:
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; } }Esto se puede expresar de manera más general de la siguiente manera, aunque no sé si sería apropiado introducir un método auxiliar de este tipo en una base de código.
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; }Si usa .NET 4.6, puede usar Task.FromException para manejar el caso de excepción, tal como usa FromResult para manejar el caso exitoso:
public Task<string> GetAnswerAsync(string question) { try { return Task.FromResult(Lookup[question]); } catch(Exception e) { return Task.FromException<string>(e); } } Si está utilizando .NET 4.5, deberá escribir su propio método FromException , pero es bastante trivial:
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; }