A partir do C # 7.0, os métodos assíncronos podem retornar ValueTask <T>. A explicação diz que ele deve ser usado quando temos um resultado em cache ou simulamos assíncrono via código síncrono. No entanto, ainda não entendo qual é o problema de usar o ValueTask sempre ou, de fato, por que o async / waitit não foi criado com um tipo de valor desde o início. Quando o ValueTask falharia ao executar o trabalho?
c#
asynchronous
Stilgar
fonte
fonte
ValueTask<T>
(em termos de alocações) não se materializar para operações que são realmente assíncronas (porque nesse casoValueTask<T>
ainda precisará de alocação de heap). Há também a questão deTask<T>
ter muito outro suporte nas bibliotecas.Respostas:
Dos documentos da API (ênfase adicionada):
fonte
Task
alocação única (que é pequena e barata hoje em dia), mas com o custo de aumentar a alocação existente do chamador e dobrar o tamanho do valor de retorno (afetando a alocação de registros). Embora seja uma escolha clara para um cenário de leitura em buffer, aplicá-lo por padrão a todas as interfaces não é algo que eu recomendaria.Task
ouValueTask
pode ser usado como um tipo de retorno síncrono (comTask.FromResult
). Mas ainda há valor (heh)ValueTask
se você tiver algo que espera ser síncrono.ReadByteAsync
sendo um exemplo clássico. Eu acredito queValueTask
foi criado principalmente para os novos "canais" (fluxos de baixo nível de byte), possivelmente também utilizados em núcleo ASP.NET onde o desempenho realmente importa.Task<T>
como padrão. Isso ocorre apenas porque a maioria dos desenvolvedores não está familiarizada com as restrições existentesValueTask<T>
(especificamente, a regra "apenas consumir uma vez" e a regra "sem bloqueio"). Dito isto, se todos os desenvolvedores da sua equipe forem bonsValueTask<T>
, eu recomendaria uma diretriz de preferência no nível da equipeValueTask<T>
.Tipos de estrutura não são livres. A cópia de estruturas maiores que o tamanho de uma referência pode ser mais lenta que a cópia de uma referência. Armazenar estruturas maiores que uma referência requer mais memória do que armazenar uma referência. Estruturas maiores que 64 bits podem não ser registradas quando uma referência pode ser registrada. Os benefícios de uma menor pressão de coleta não podem exceder os custos.
Os problemas de desempenho devem ser abordados com uma disciplina de engenharia. Estabeleça metas, avalie seu progresso em relação às metas e, em seguida, decida como modificar o programa se as metas não forem cumpridas, medindo ao longo do caminho para garantir que suas alterações sejam realmente melhorias.
await
foi adicionado ao C # por muito tempo após oTask<T>
tipo já existir. Teria sido um pouco perverso inventar um novo tipo quando ele já existisse. Eawait
passou por muitas iterações de design antes de escolher a que foi enviada em 2012. O perfeito é o inimigo do bem; melhor enviar uma solução que funcione bem com a infraestrutura existente e, se houver demanda do usuário, forneça melhorias posteriormente.Observo também que o novo recurso de permitir que tipos fornecidos pelo usuário sejam a saída de um método gerado pelo compilador adiciona riscos e encargos de teste consideráveis. Quando as únicas coisas que você pode retornar são nulas ou uma tarefa, a equipe de teste não precisa considerar nenhum cenário em que algum tipo absolutamente louco seja retornado. Testar um compilador significa descobrir não apenas quais programas as pessoas provavelmente escreverão, mas quais programas serão possíveis de escrever, porque queremos que o compilador compile todos os programas legais, não apenas todos os programas sensíveis. Isso é caro.
O objetivo da coisa é melhorar o desempenho. Não funciona se não melhora o desempenho de forma mensurável e significativa . Não há garantia de que sim.
fonte
ValueTask<T>
não é um subconjuntoTask<T>
, é um superconjunto .Assim, você pode escrever um método assíncrono ou síncrono, em vez de escrever um método idêntico para cada um. Você pode usá-lo em qualquer lugar que usar,
Task<T>
mas muitas vezes não adicionaria nada.Bem, isso acrescenta uma coisa: adiciona uma promessa implícita ao chamador de que o método realmente usa a funcionalidade adicional que
ValueTask<T>
fornece. Pessoalmente, prefiro escolher os tipos de parâmetro e retorno que informam ao chamador o máximo possível. Não retorneIList<T>
se a enumeração não puder fornecer uma contagem; não volteIEnumerable<T>
se puder. Seus consumidores não precisam procurar nenhuma documentação para saber quais dos seus métodos podem ser razoavelmente chamados de forma síncrona e quais não.Não vejo mudanças no design futuro como um argumento convincente lá. Muito pelo contrário: se um método alterar sua semântica, ele deve interromper a construção até que todas as chamadas sejam atualizadas de acordo. Se isso for considerado indesejável (e acredite, sou solidário com o desejo de não quebrar a compilação), considere o versionamento da interface.
É para isso que serve a digitação forte.
Se alguns dos programadores que projetam métodos assíncronos em sua loja não puderem tomar decisões informadas, pode ser útil designar um mentor sênior para cada um desses programadores menos experientes e fazer uma revisão semanal do código. Se eles acharem errado, explique por que isso deve ser feito de maneira diferente. É uma sobrecarga para os caras mais velhos, mas isso levará os juniores a acelerar muito mais rapidamente do que apenas jogá-los no fundo do poço e dar a eles alguma regra arbitrária a seguir.
Se o cara que escreveu o método não sabe se pode ser chamado de forma síncrona, quem sabe ?!
Se você tem tantos programadores inexperientes escrevendo métodos assíncronos, essas mesmas pessoas também os chamam? Eles estão qualificados para descobrir por si mesmos quais são seguros para chamar de assíncrono ou começarão a aplicar uma regra igualmente arbitrária à maneira como chamam essas coisas?
O problema aqui não são seus tipos de retorno, são os programadores sendo colocados em funções para as quais não estão prontos. Isso deve ter acontecido por um motivo, por isso tenho certeza de que não pode ser trivial de corrigir. Descrevê-lo certamente não é uma solução. Mas procurar uma maneira de ocultar o problema além do compilador também não é uma solução.
fonte
ValueTask<T>
, presumo que o cara que escreveu o método fez isso porque o método realmente usa a funcionalidadeValueTask<T>
adicionada. Não entendo por que você acha desejável que todos os seus métodos tenham o mesmo tipo de retorno. Qual é o objetivo lá?object
porque talvez um dia você queira que eles retornem emint
vez destring
.Há algumas mudanças no .Net Core 2.1 . A partir do .net core 2.1, o ValueTask pode representar não apenas as ações concluídas síncronas, mas também a assíncrona. Além disso, recebemos o
ValueTask
tipo não genérico .Deixarei o comentário de Stephen Toub relacionado à sua pergunta:
O recurso pode ser usado não apenas no .net core 2.1. Você poderá usá-lo com o pacote System.Threading.Tasks.Extensions .
fonte