Estou trabalhando em um projeto que precisa oferecer suporte à versão assíncrona e sincronizada de uma mesma lógica / método. Então, por exemplo, eu preciso ter:
public class Foo
{
public bool IsIt()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return conn.Query<bool>("SELECT IsIt FROM SomeTable");
}
}
public async Task<bool> IsItAsync()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return await conn.QueryAsync<bool>("SELECT IsIt FROM SomeTable");
}
}
}
A lógica assíncrona e sincronizada para esses métodos é idêntica em todos os aspectos, exceto que uma é assíncrona e a outra não. Existe uma maneira legítima de evitar violar o princípio DRY nesse tipo de cenário? Vi que as pessoas dizem que você pode usar GetAwaiter (). GetResult () em um método assíncrono e invocá-lo a partir do seu método de sincronização? Esse segmento é seguro em todos os cenários? Existe outra maneira melhor de fazer isso ou sou forçado a duplicar a lógica?
c#
.net
asynchronous
Marko
fonte
fonte
return Task.FromResult(IsIt());
)Respostas:
Você fez várias perguntas na sua pergunta. Vou dividi-los um pouco diferente do que você fez. Mas primeiro deixe-me responder diretamente à pergunta.
Todos nós queremos uma câmera leve, de alta qualidade e barata, mas, como diz o ditado, você pode obter no máximo duas dessas três. Você está na mesma situação aqui. Você deseja uma solução eficiente, segura e que compartilhe código entre os caminhos síncrono e assíncrono. Você só vai conseguir dois desses.
Deixe-me explicar por que isso é. Vamos começar com esta pergunta:
O objetivo desta pergunta é "posso compartilhar os caminhos síncronos e assíncronos fazendo com que o caminho síncrono simplesmente faça uma espera síncrona na versão assíncrona?"
Deixe-me ser super claro neste ponto, porque é importante:
VOCÊ DEVE IMEDIATAMENTE PARAR DE TER QUALQUER CONSELHO DESSAS PESSOAS .
Esse é um conselho extremamente ruim. É muito perigoso buscar resultados de uma tarefa assíncrona de forma síncrona, a menos que você tenha evidências de que a tarefa foi concluída normalmente ou de forma anormal .
A razão pela qual esse conselho é extremamente ruim é, bem, considere esse cenário. Você deseja cortar a grama, mas a lâmina do cortador de grama está quebrada. Você decide seguir este fluxo de trabalho:
O que acontece? Você dorme para sempre porque agora a operação de verificar o correio está bloqueada em algo que acontece depois que o correio chega .
É extremamente fácil entrar nessa situação quando você espera de forma síncrona uma tarefa arbitrária. Essa tarefa pode ter um trabalho agendado no futuro do encadeamento que está aguardando e agora esse futuro nunca chegará porque você está esperando por ele.
Se você fizer uma espera assíncrona , tudo estará bem! Você verifica periodicamente o correio e, enquanto espera, faz um sanduíche ou paga seus impostos ou o que for; você continua fazendo o trabalho enquanto espera.
Nunca espere de forma síncrona. Se a tarefa for concluída, é desnecessária . Se a tarefa não for concluída, mas agendada para executar o encadeamento atual, será ineficiente porque o encadeamento atual pode estar atendendo outro trabalho em vez de aguardar. Se a tarefa não for concluída e o agendamento for executado no encadeamento atual, ele ficará suspenso para aguardar síncrona. Não há um bom motivo para esperar de forma síncrona, novamente, a menos que você já saiba que a tarefa está concluída .
Para ler mais sobre este tópico, consulte
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Stephen explica o cenário do mundo real muito melhor do que eu.
Agora vamos considerar a "outra direção". Podemos compartilhar código fazendo a versão assíncrona simplesmente fazer a versão síncrona em um thread de trabalho?
Essa é possivelmente e de fato provavelmente uma má ideia, pelas seguintes razões.
É ineficiente se a operação síncrona for um trabalho de E / S de alta latência. Isso contrata essencialmente um trabalhador e o faz dormir até que uma tarefa seja concluída. Threads são incrivelmente caros . Eles consomem um milhão de bytes de espaço de endereço mínimo por padrão, levam tempo, consomem recursos do sistema operacional; você não quer queimar um fio fazendo um trabalho inútil.
A operação síncrona pode não ter sido gravada como segura para threads.
Essa é uma técnica mais razoável se o trabalho de alta latência estiver vinculado ao processador, mas se for, provavelmente você não deseja simplesmente entregá-lo a um encadeamento de trabalho. Você provavelmente deseja usar a biblioteca paralela de tarefas para paralelá-la ao maior número possível de CPUs, provavelmente deseja lógica de cancelamento e não pode simplesmente fazer com que a versão síncrona faça tudo isso, porque já seria a versão assíncrona .
Leitura adicional; novamente, Stephen explica muito claramente:
Por que não usar o Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
Mais cenários de "faça e não faça" para o Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html
O que isso nos deixa então? Ambas as técnicas para compartilhar código levam a impasses ou grandes ineficiências. A conclusão a que chegamos é que você deve fazer uma escolha. Deseja um programa eficiente, correto e que encante o chamador, ou deseja salvar algumas pressionamentos de tecla, duplicando uma pequena quantidade de código entre os caminhos síncrono e assíncrono? Você não recebe os dois, receio.
fonte
Task.Run(() => SynchronousMethod())
na versão assíncrona não é o que o OP deseja?Dns.GetHostEntryAsync
é implementado no .NET eFileStream.ReadAsync
em certos tipos de arquivos. O sistema operacional simplesmente não fornece interface assíncrona, portanto o tempo de execução precisa ser falsificado (e não é específico do idioma - por exemplo, o tempo de execução Erlang executa uma árvore inteira de processos de trabalho , com vários threads dentro de cada um, para fornecer E / S e nome de disco sem bloqueio resolução).É difícil dar uma resposta única para essa pergunta. Infelizmente, não existe uma maneira simples e perfeita de obter reutilização entre código assíncrono e síncrono. Mas aqui estão alguns princípios a considerar:
Query()
em um eQueryAsync()
no outro) ou configurando conexões com configurações diferentes. Portanto, mesmo quando estruturalmente semelhante, muitas vezes há diferenças de comportamento suficientes para merecê-las sendo tratadas como código separado com requisitos diferentes. Observe as diferenças entre as implementações de métodos Async e Sync na classe File, por exemplo: nenhum esforço é feito para fazê-los usar o mesmo códigoTask.FromResult(...)
.Boa sorte.
fonte
Fácil; faça com que o síncrono chame o assíncrono. Existe até um método útil
Task<T>
para fazer exatamente isso:fonte