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

436

En el siguiente código, debido a la interfaz, la clase LazyBardebe devolver una tarea de su método (y por el bien de los argumentos no se puede cambiar). Si LazyBarla implementación de s 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 del método?

He seguido a 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 el bien de los argumentos, digamos cientos de veces por segundo):

  • ¿Este azúcar sintáctico se desenrolla en algo grande?
  • ¿Comienza a obstruir el grupo de subprocesos de mi aplicación?
  • ¿El compilador es suficiente para tratar de manera Delay(0)diferente?
  • ¿ return Task.Run(() => { });Sería diferente?

¿Hay una mejor manera?

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();
        }
    }
}
Jon Rea
fuente
8
En lo personal me gustaría ir con Task.FromResult<object>(null).
CodesInChaos

Respuestas:

626

Usar Task.FromResult(0)o Task.FromResult<object>(null)generará menos gastos generales que crear un Taskcon una expresión no operativa. Al crear un Taskresultado predeterminado, no hay gastos generales de programación involucrados.


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

Reed Copsey
fuente
55
Y si está utilizando github.com/StephenCleary/AsyncEx , proporcionan una clase TaskConstants para proporcionar estas tareas completadas junto con varias otras bastante útiles (0 int, true / false, Default <T> ())
quentin-starin
55
return default(YourReturnType);
Legends
8
@Legends Eso no funciona para crear una tarea directamente
Reed Copsey
18
No estoy seguro, pero Task.CompletedTaskpodría hacer el truco! (pero requiere .net 4.6)
Peter
187

Para agregar a la respuesta de Reed Copsey sobre el uso 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 iguales:

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

Con TaskExtensions.CompletedTaskusted 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 Task.CompletedTaskpropiedad estática

Task completedTask = Task.CompletedTask;
i3arnon
fuente
¿Necesito devolverlo o esperarlo ?
Pixar
@Pixar, ¿qué quieres decir? Puede hacer ambas cosas, pero esperarlo continuará sincrónicamente.
i3arnon
Lo siento, tuve que mencionar el contexto :) Como lo veo ahora, podemos hacer public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()lo mismo que public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Entonces, podemos return CompletedTask;o bien await CompletedTask;. ¿Qué es más preferible (quizás más eficiente o más congruente)?
Pixar
3
@Pixar no quiero borrar. Quise decir "'no-async' sería más eficiente". Hacer un método asíncrono le indica al compilador que lo transforme en una máquina de estado. También creará una nueva tarea cada vez que la llame. Devolver una tarea ya completada sería más claro y más eficaz.
i3arnon
3
@Asad reduce las asignaciones (y con ello el tiempo de GC). En lugar de asignar nueva memoria y construir una instancia de Tarea cada vez que necesita una Tarea completada, solo hace esto una vez.
i3arnon
38

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

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

La naturaleza de la caché ni se garantiza que se mantiene constante, pero como optimizaciones dependientes de la implementación que sólo depende de la implementación como optimizaciones (es decir, que todavía iba a trabajar correctamente si la aplicación cambia a algo que todavía era válida) el uso de la Task.Delay(0)era mejor que la respuesta aceptada.

Jon Hanna
fuente
1
Todavía estoy usando 4.5 y cuando investigué un poco, me divertí al encontrar que Task.Delay (0) es un caso especial para devolver un miembro estático de CompletedTask. Que luego almacené en caché en mi propio miembro estático de CompletedTask. : P
Darren Clark
2
No sé por qué, pero Task.CompletedTaskno puedo usarlo en un proyecto PCL, incluso si configuro la versión .net en 4.6 (perfil 7), que acabo de probar en VS2017.
Felix
@Fay Supongo que no debe ser parte de la superficie API de PCL, aunque en este momento lo único que hace algo que sea compatible con PCL también es compatible con 4.5, así que ya tengo que usar el mío Task.CompletedTask => Task.Delay(0);para admitir eso, así que no No estoy seguro de la parte superior de mi cabeza.
Jon Hanna
17

Recientemente encontré esto y seguí recibiendo advertencias / errores sobre el método que se anula.

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 realmente esté haciendo algo en el método.

Alexander Trauzzi
fuente
17
Eso está completamente mal. Está obteniendo un error del compilador porque la definición del método contiene asíncrono, por lo que el compilador espera una espera. El uso "correcto" sería público Task MyVoidAsyncMethog () {return Task.CompletedTask;}
Keith
3
No sé por qué esto era por votación ya que parece ser la respuesta más limpia
webwake
3
Por el comentario de Keith.
noelicus
44
No está totalmente equivocado, acaba de eliminar la palabra clave asíncrona. Mi enfoque es más idiomático. El suyo es minimalista. Si no es un poco grosero.
Alexander Trauzzi
1
Esto no tiene sentido, estoy completamente de acuerdo con Keith aquí, en realidad no recibo todos los votos positivos. ¿Por qué agregaría código que no es necesario? public Task MyVoidAsyncMethod() {}es completamente el mismo que el método anterior. Si hay un caso de uso para usarlo así, agregue el código adicional.
Nick N.
12
return Task.CompletedTask; // this will make the compiler happy
Xin
fuente
9

Cuando debe devolver el tipo especificado:

Task.FromResult<MyClass>(null);
trashmaker_
fuente
3

Prefiero la Task completedTask = Task.CompletedTask;solución de .Net 4.6, pero otro enfoque es marcar el método como asíncrono y devolver vacío:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Recibirá una advertencia (CS1998 - Función asíncrona sin esperar expresión), pero es seguro ignorarla en este contexto.

Remco te Wierik
fuente
1
Si su método devuelve nulo, puede tener problemas con excepciones.
Adam Tuliper - MSFT
0

Si está utilizando genéricos, todas las respuestas nos darán un error de compilación. Puedes usar return default(T);. Muestra a continuación para explicar más.

public async Task<T> GetItemAsync<T>(string id)
            {
                try
                {
                    var response = await this._container.ReadItemAsync<T>(id, new PartitionKey(id));
                    return response.Resource;
                }
                catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
                {

                    return default(T);
                }

            }
Karthikeyan VK
fuente
¿Por qué el voto negativo?
Karthikeyan VK
La pregunta no era acerca de los métodos asíncronos :)
Frode Nilsen
0
return await Task.FromResult(new MyClass());
JMH
fuente
3
Si bien este código puede resolver la pregunta, incluir una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación, y probablemente resultaría en más votos positivos. Recuerde que está respondiendo la pregunta para los lectores en el futuro, no solo la persona que pregunta ahora. Por favor, editar su respuesta para agregar explicaciones y dar una indicación de lo que se aplican limitaciones y supuestos.
David Buck