Diferencia entre regresar y esperar una Tarea en un método asíncrono

¿Hay alguna diferencia entre los métodos a continuación? ¿Es preferible uno sobre el otro?

public static async Task SendAsync1(string to, string subject, string htmlBody) { // ... await smtp.SendMailAsync(message); // No return statement } public static Task SendAsync2(string to, string subject, string htmlBody) { // ... return smtp.SendMailAsync(message); } 

Este método será llamado desde los métodos de controlador MVC; por ejemplo:

 public async Task RegisterUser(RegisterViewModel model) { // ... await Mailer.SendAsync(user.Email, subject, body); return View(model); } 

Hay 2 diferencias prácticas:

  1. La segunda opción no creará el mecanismo de estado de la máquina que permite el uso async-await . Eso tendrá un efecto positivo menor en el rendimiento.
  2. El manejo de excepciones sería un poco diferente. Cuando marca un método como async cualquier excepción se almacena en la tarea devuelta (tanto desde la parte asíncrona como la síncrona) y se lanza solo cuando la tarea está aguardada (o esperada). Cuando no es async , las excepciones de las partes síncronas actúan como en cualquier otro método.

Mi sugerencia : utilice la segunda para boost el rendimiento, pero esté atento a las excepciones y errores.


Un ejemplo que muestra la diferencia:

 public static async Task Test() { Task pending = Task.FromResult(true); try { pending = SendAsync1(); } catch (Exception) { Console.WriteLine("1-sync"); } try { await pending; } catch (Exception) { Console.WriteLine("1-async"); } pending = Task.FromResult(true); try { pending = SendAsync2(); } catch (Exception) { Console.WriteLine("2-sync"); } try { await pending; } catch (Exception) { Console.WriteLine("2-async"); } } public static async Task SendAsync1() { throw new Exception("Sync Exception"); await Task.Delay(10); } public static Task SendAsync2() { throw new Exception("Sync Exception"); return Task.Delay(10); } 

Salida:

 1-async 2-sync 

Antes que nada, no creo que compile el código de tu ejemplo. Debe eliminar la palabra clave ‘async’ de SendAsync2.

Si haces eso, entonces estos métodos se pueden usar indistintamente, entonces no, no hay diferencia en este caso. Preferiría el que no tiene async / await.

Sin embargo, hay casos en los que parece que no hay diferencia, pero la diferencia radica en los detalles. Considere por ejemplo este código:

 async Task Get() { using (something) { return await GetX(); } } 

Si fueras a cambiar esto a:

 Task Get() { using (something) { return GetX(); } } 

entonces el bloque de using ya no protege la ejecución encapsulada en x, y something será eliminado antes de lo que sería en el primer caso. Importante, por ejemplo, cuando something es un contexto de Entity Framework.

Lo mismo ocurre con el return await dentro de los bloques de try .

Su primer método que await hará que el comstackdor cree una máquina de estados, ya que una vez que se the await palabra clave await, el método regresará a la persona que llama, y ​​una vez que se complete la parte esperada, debe reanudarse desde el punto en que se detuvo.

Pero, como no está haciendo nada después de esperar (no hay continuación), no hay necesidad de esa máquina de estado.

El segundo método es preferido en este caso, y puede omitir la palabra clave async , ya que no está esperando nada