c# - subir - Si mi interfaz debe devolver Tarea, ¿cuál es la mejor manera de tener una implementación sin operación?
tarea devuelta en classroom (5)
En el código a continuación, debido a la interfaz, la clase LazyBar
debe devolver una tarea desde su método (y, por motivos, 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?
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, digamos cientos de veces por segundo):
- ¿Este azúcar sintáctico desenrolla a algo grande?
- ¿Comienza a obstruir el grupo de subprocesos de mi aplicación?
- ¿Es la cuchilla del compilador suficiente para lidiar con
Delay(0)
diferente? -
return Task.Run(() => { });
ser diferente?
¿Hay alguna manera mejor?
using System.Threading.Tasks;
namespace MyAsyncTest
{
internal interface IFooFace
{
Task WillBeLongRunningAsyncInTheMajorityOfImplementations();
}
/// <summary>
/// An implementation, that unlike most cases, will not have a long-running
/// operation in ''WillBeLongRunningAsyncInTheMajorityOfImplementations''
/// </summary>
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<int>(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<object>(null)
incurrirá en menos sobrecarga que crear una Task
con una expresión sin operación. Al crear una Task
con un resultado predeterminado, no hay gastos generales de programació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 el rendimiento aún más si almacena en caché la tarea ya completada ya que todas las instancias de tareas completadas son las mismas:
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 última versión de .Net Framework (v4.6) agrega solo eso con la propiedad estática Task.CompletedTask
Task completedTask = Task.CompletedTask;
Prefiero la Task completedTask = Task.CompletedTask;
solución de .Net 4.6, pero otro enfoque es marcar el método asíncrono y devolver el vacío:
public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
{
}
Recibirá una advertencia (CS1998 - Función asíncrona sin esperar la expresión), pero es seguro ignorarlo en este contexto.
Recientemente encontré esto y seguí recibiendo advertencias / errores sobre la anulación del método.
Estamos en el negocio de aplacar el compilador 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 una declaración de devolución a menos que esté haciendo algo en el método.
Task.Delay(0)
como en la respuesta aceptada fue un buen enfoque, ya que es una copia en caché de una Task
completada.
A partir del 4.6 ahora hay Task.CompletedTask
que es más explícito en su propósito, pero no solo hace que Task.Delay(0)
devuelva una sola instancia en caché, sino que también devuelve la misma instancia en caché que Task.CompletedTask
.
Se garantiza que la naturaleza almacenada en caché de ninguno de los dos permanecerá constante, pero como 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 la Task.Delay(0)
fue mejor que la respuesta aceptada.