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:
Vi que as pessoas dizem que você poderia usar GetAwaiter().GetResult()
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?
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:
- Peça um novo blade em um site. Esta é uma operação assíncrona de alta latência.
- Espere de forma síncrona - ou seja, durma até ter a lâmina na mão .
- Verifique periodicamente a caixa de correio para ver se o blade chegou.
- Retire a lâmina da caixa. Agora você tem na mão.
- Instale a lâmina no cortador de grama.
- Cortar a grama.
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.