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




editarla? subir (5)

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.

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();
        }
    }
}

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.


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;





c# .net task-parallel-library async-await threadpool