Si mi interfaz debe devolver Tarea, ¿cuál es la mejor manera de tener una implementación sin operación?

En el siguiente código, debido a la interfaz, la clase LazyBar debe devolver una tarea desde su método (y por razones de argumentos no se puede cambiar). Si la implementación de LazyBar es inusual en el sentido de que se ejecuta de forma rápida y sincrónica, ¿cuál es la mejor manera de devolver una tarea Sin Operación desde el método?

Me he ido con Task.Delay(0) continuación, sin embargo, me gustaría saber si esto tiene algún efecto secundario en el rendimiento si la función se llama mucho (por razones de argumento, digamos cientos de veces por segundo):

  • ¿Este azúcar sintáctico desaparece para convertirse en algo grande?
  • ¿Comienza a obstruir el grupo de subprocesos de mi aplicación?
  • ¿Es suficiente el comstackdor para lidiar con Delay(0) diferente?
  • return Task.Run(() => { }); ser diferente?

¿Hay una mejor manera?

 using System.Threading.Tasks; namespace MyAsyncTest { internal interface IFooFace { Task WillBeLongRunningAsyncInTheMajorityOfImplementations(); } ///  /// An implementation, that unlike most cases, will not have a long-running /// operation in 'WillBeLongRunningAsyncInTheMajorityOfImplementations' ///  internal class LazyBar : IFooFace { #region IFooFace Members public Task WillBeLongRunningAsyncInTheMajorityOfImplementations() { // First, do something really quick var x = 1; // Can't return 'null' here! Does 'Task.Delay(0)' have any performance considerations? // Is it a real no-op, or if I call this a lot, will it adversely affect the // underlying thread-pool? Better way? return Task.Delay(0); // Any different? // return Task.Run(() => { }); // If my task returned something, I would do: // return Task.FromResult(12345); } #endregion } internal class Program { private static void Main(string[] args) { Test(); } private static async void Test() { IFooFace foo = FactoryCreate(); await foo.WillBeLongRunningAsyncInTheMajorityOfImplementations(); return; } private static IFooFace FactoryCreate() { return new LazyBar(); } } } 

El uso de Task.FromResult(0) o Task.FromResult(null) tendrá menos gastos generales que la creación de una Task con una expresión no operativa. Al crear una Task con un resultado predeterminado, no hay gastos indirectos de progtwigción involucrados.


Hoy, recomendaría usar Task.CompletedTask para lograr esto.

Para agregar a la respuesta de Reed Copsey sobre el uso de Task.FromResult , puede mejorar aún más el rendimiento si almacena en caché la tarea ya completada, ya que todas las instancias de tareas completadas son iguales:

 public static class TaskExtensions { public static readonly Task CompletedTask = Task.FromResult(false); } 

Con TaskExtensions.CompletedTask puede usar la misma instancia en todo el dominio de la aplicación.


La versión más reciente de .Net Framework (v4.6) agrega eso con la propiedad estática Task.CompletedTask

 Task completedTask = Task.CompletedTask; 

Task.Delay(0) como estaba en la respuesta fue un buen enfoque, ya que es una copia en caché de una Task completada.

A partir de 4.6 ahora hay Task.CompletedTask que es más explícito en su propósito, pero no solo Task.Delay(0) aún devuelve una sola instancia en caché, sino que devuelve la misma instancia en caché individual que Task.CompletedTask .

No se garantiza que la naturaleza en caché de ninguno de los dos permanezca constante, pero como las optimizaciones dependientes de la implementación que solo dependen de la implementación como optimizaciones (es decir, aún funcionarían correctamente si la implementación cambiara a algo que todavía era válido) el uso de Task.Delay(0) fue mejor que la respuesta aceptada.

Recientemente me encontré con esto y seguí recibiendo advertencias / errores sobre el método siendo nulo.

Estamos en el negocio de aplacar el comstackdor y esto lo aclara:

  public async Task MyVoidAsyncMethod() { await Task.CompletedTask; } 

Esto reúne el mejor de todos los consejos aquí hasta ahora. No es necesaria ninguna statement de devolución a menos que realmente esté haciendo algo en el método.

Prefiero la Task completedTask = Task.CompletedTask; solución de .Net 4.6, pero otro enfoque es marcar el método async y return void:

  public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations() { } 

Aparecerá una advertencia (CS1998 – Función asíncrona sin aguardar expresión), pero es seguro ignorarlo en este contexto.