Se minha interface precisar retornar a tarefa, qual é a melhor maneira de ter uma implementação sem operação?

435

No código abaixo, devido à interface, a classe LazyBardeve retornar uma tarefa de seu método (e, por razões de argumento, não pode ser alterada). Se LazyBara implementação de s é incomum, pois ela é executada de forma rápida e síncrona - qual é a melhor maneira de retornar uma tarefa sem operação do método?

Eu segui Task.Delay(0)abaixo, no entanto, gostaria de saber se isso tem algum efeito colateral no desempenho se a função for chamada muito (por uma questão de argumentos, digamos centenas de vezes por segundo):

  • Esse açúcar sintático desenrola-se em algo grande?
  • Ele começa a entupir o pool de threads do meu aplicativo?
  • O cutelo do compilador é suficiente para lidar de maneira Delay(0)diferente?
  • Seria return Task.Run(() => { });diferente?

Existe uma maneira melhor?

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
fonte
8
Pessoalmente, eu iria Task.FromResult<object>(null).
CodesInChaos

Respostas:

624

O uso Task.FromResult(0)ou Task.FromResult<object>(null)incorrerá em menos sobrecarga do que a criação de uma Taskexpressão com no-op. Ao criar um Taskcom um resultado predeterminado, não há sobrecarga de agendamento envolvida.


Hoje, eu recomendaria o uso de Task.CompletedTask para fazer isso.

Reed Copsey
fonte
5
E se você estiver usando github.com/StephenCleary/AsyncEx, eles fornecem uma classe TaskConstants para fornecer essas tarefas concluídas, além de várias outras bastante úteis (0 int, true / false, Default <T> ())
quentin-starin
5
return default(YourReturnType);
Legendas
8
@Legends que não funciona para a criação de uma tarefa diretamente
Reed Copsey
18
não tenho certeza, mas Task.CompletedTaskpode fazer o truque! (mas requer .net 4.6) #
Peter Peter
1
Pergunta: como é que Task.FromResult<TResult>, que retorna a Task<TResult>, satisfaz o tipo de retorno de Task(sem parâmetros de tipo)?
Rog.ap 23/08
187

Para adicionar à resposta de Reed Copsey sobre o uso Task.FromResult, você pode melhorar ainda mais o desempenho se armazenar em cache a tarefa já concluída, pois todas as instâncias das tarefas concluídas são as mesmas:

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

Com TaskExtensions.CompletedTaskvocê, você pode usar a mesma instância em todo o domínio do aplicativo.


A versão mais recente do .Net Framework (v4.6) adiciona exatamente isso à Task.CompletedTaskpropriedade estática

Task completedTask = Task.CompletedTask;
i3arnon
fonte
Preciso devolvê- lo ou aguardá -lo?
Pixar
@Pixar, o que você quer dizer? Você pode fazer as duas coisas, mas aguardar continuará em sincronia.
I3arnon 03/08/2015
Desculpe, eu tive que mencionar o contexto :) Como eu o vejo agora, podemos fazer public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()o mesmo public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Então, podemos return CompletedTask;ou await CompletedTask;. O que é mais preferível (talvez mais eficiente ou mais congruente)?
Pixar
3
@Pixar Eu não estou claro. Eu quis dizer "'não-assíncrono' seria mais eficiente". Criar um método assíncrono instrui o compilador a transformá-lo em uma máquina de estado. Também criará uma nova tarefa sempre que você a chamar. O retorno de uma tarefa já concluída seria mais claro e com melhor desempenho.
I3arnon 03/08/2015
3
@Asad reduz alocações (e com ele tempo de GC). Em vez de alocar nova memória e construir uma instância de tarefa cada vez que você precisar de uma tarefa concluída, faça isso apenas uma vez.
I3arnon 7/08/2015
38

Task.Delay(0)como na resposta aceita, foi uma boa abordagem, pois é uma cópia em cache de uma conclusão Task.

A partir da versão 4.6, agora Task.CompletedTaskexiste um objetivo mais explícito, mas não apenas Task.Delay(0)ainda retorna uma instância em cache, como também retorna a mesma instância em cache Task.CompletedTask.

A natureza em cache não é garantido para permanecer constante, mas como otimizações dependente de implementação que são apenas como otimizações dependente de implementação (isto é, eles ainda funcionam corretamente se a implementação alterado para algo que ainda era válida) o uso de Task.Delay(0)era melhor que a resposta aceita.

Jon Hanna
fonte
1
Ainda estou usando o 4.5 e quando fiz algumas pesquisas, achei divertido descobrir que o Task.Delay (0) é especial para retornar um membro estático do CompletedTask. Que, em seguida, eu armazenei em cache no meu próprio membro estático CompletedTask. : P
Darren Clark
2
Não sei por que, mas Task.CompletedTasknão posso ser usado no projeto PCL, mesmo se eu definir a versão .net para 4.6 (perfil 7), testada no VS2017.
Felix
@Fay Eu acho que não deve fazer parte da superfície da API do PCL, embora a única coisa que faça algo no momento que suporte o PCL também suporte o 4.5, então eu já tenho que usar o meu próprio Task.CompletedTask => Task.Delay(0);para suportar isso, então eu não tenho certeza do topo da minha cabeça.
Jon Hanna
17

Recentemente encontrei isso e continuei recebendo avisos / erros sobre o método sendo nulo.

Estamos no negócio de aplacar o compilador e isso esclarece:

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

Isso reúne o melhor de todos os conselhos aqui até agora. Nenhuma declaração de retorno é necessária, a menos que você esteja realmente fazendo algo no método.

Alexander Trauzzi
fonte
17
Isso está completamente errado. Você está recebendo um erro do compilador porque a definição do método contém assíncrona; portanto, o compilador espera uma espera. O uso "correto" seria público Task MyVoidAsyncMethog () {return Task.CompletedTask;} #
Keith
3
Não sei por que isso foi para baixo votado como esta parece ser a resposta mais limpa
webwake
3
Por causa do comentário de Keith.
noelicus
4
Ele não está totalmente errado, apenas removeu a palavra-chave assíncrona. Minha abordagem é mais idiomática. O dele é minimalista. Se não for um pouco rude.
Alexander Trauzzi
1
Isso não faz sentido, concordo completamente com Keith aqui, eu não recebo todos os votos positivos na verdade. Por que você adicionaria código desnecessário? public Task MyVoidAsyncMethod() {}é completamente igual ao método acima. Se houver um caso de uso para usá-lo dessa forma, adicione o código adicional.
Nick N.
12
return Task.CompletedTask; // this will make the compiler happy
Xin
fonte
9

Quando você deve retornar o tipo especificado:

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

Prefiro a Task completedTask = Task.CompletedTask;solução do .Net 4.6, mas outra abordagem é marcar o método como assíncrono e retornar nulo:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Você receberá um aviso (CS1998 - Função assíncrona sem aguardar expressão), mas é seguro ignorar neste contexto.

Remco te Wierik
fonte
1
Se o seu método retornar nulo, você poderá ter problemas com exceções.
Adam Tuliper - MSFT
0

Se você estiver usando genéricos, todas as respostas nos fornecerão um erro de compilação. Você pode usar return default(T);. Exemplo abaixo para explicar mais.

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
fonte
Por que o voto negativo?
Karthikeyan VK
A pergunta não era sobre métodos assíncronos :)
Frode Nilsen
0
return await Task.FromResult(new MyClass());
JMH
fonte
3
Embora esse código possa resolver a questão, incluir uma explicação de como e por que isso resolve o problema realmente ajudaria a melhorar a qualidade da sua postagem e provavelmente resultaria em mais votos positivos. Lembre-se de que você está respondendo à pergunta dos leitores no futuro, não apenas à pessoa que está perguntando agora. Por favor edite sua resposta para adicionar explicações e dar uma indicação do que limitações e premissas se aplicam.
David Buck