Esta pergunta diz respeito à linguagem C #, mas espero que abranja outras linguagens como Java ou TypeScript.
A Microsoft recomenda práticas recomendadas sobre o uso de chamadas assíncronas no .NET. Entre essas recomendações, vamos escolher duas:
- altere a assinatura dos métodos assíncronos para que eles retornem Tarefa ou Tarefa <> (no TypeScript, isso seria uma promessa <>)
- altere os nomes dos métodos assíncronos para terminar com xxxAsync ()
Agora, ao substituir um componente síncrono de baixo nível por um componente assíncrono, isso afeta toda a pilha do aplicativo. Como o assíncrono / espera tem um impacto positivo apenas se usado "até o fim", significa que os nomes de assinatura e método de cada camada no aplicativo devem ser alterados.
Uma boa arquitetura geralmente envolve a colocação de abstrações entre cada camada, de forma que a substituição de componentes de baixo nível por outras não seja vista pelos componentes de nível superior. Em C #, as abstrações assumem a forma de interfaces. Se introduzirmos um novo componente assíncrono de baixo nível, cada interface na pilha de chamadas precisará ser modificada ou substituída por uma nova interface. A maneira como um problema é resolvido (assíncrono ou sincronizado) em uma classe de implementação não está mais oculto (abstraído) para os chamadores. Os chamadores precisam saber se é sincronizado ou assíncrono.
As melhores práticas assíncronas / aguardam não estão em contradição com os princípios da "boa arquitetura"?
Isso significa que cada interface (por exemplo, IEnumerable, IDataAccessLayer) precisa de sua contrapartida assíncrona (IAsyncEnumerable, IAsyncDataAccessLayer), para que possam ser substituídas na pilha ao alternar para dependências assíncronas?
Se formos um pouco mais longe, não seria mais simples supor que todos os métodos sejam assíncronos (retornar uma tarefa <> ou Promessa <>) e que os métodos sincronizem as chamadas assíncronas quando elas não são realmente assíncrono? Isso é algo que se espera das futuras linguagens de programação?
fonte
CancellationToken
usar a e aqueles que o fazem podem desejar fornecer um padrão). A remoção dos métodos de sincronização existentes (e a quebra proativa de todo o código) é óbvia para iniciantes.Respostas:
Qual a cor da sua função?
Talvez você esteja interessado em Qual a cor de Bob Nystrom 1 .
Neste artigo, ele descreve uma linguagem fictícia em que:
Embora fictício, isso acontece regularmente nas linguagens de programação:
this
.Como você percebeu, devido a essas regras, as funções vermelhas tendem a se espalhar pela base de código. Você insere um e, aos poucos, coloniza toda a base de código.
1 Bob Nystrom, além dos blogs, também faz parte da equipe da Dart e escreveu esta pequena série de intérpretes de artesanato; altamente recomendado para qualquer aficionado por linguagem de programação / compilador.
2 Não é bem verdade, como você pode chamar uma função assíncrona e bloquear até que ela retorne, mas ...
Limitação de idioma
Essa é, essencialmente, uma limitação de idioma / tempo de execução.
O idioma com o encadeamento M: N, por exemplo, como Erlang e Go, não possui
async
funções: cada função é potencialmente assíncrona e sua "fibra" será simplesmente suspensa, trocada e trocada novamente quando estiver pronta novamente.O C # seguiu um modelo de segmentação 1: 1 e, portanto, decidiu apresentar a sincronicidade na linguagem para evitar o bloqueio acidental de threads.
Na presença de limitações de idioma, as diretrizes de codificação precisam se adaptar.
fonte
async
); de fato, era um padrão bastante comum para a criação de UI responsiva. Era tão bonito quanto Erlang? Não. Mas isso ainda é muito distante da C :)Você está certo, há uma contradição aqui, mas as "melhores práticas" não são ruins. É porque a função assíncrona faz algo essencialmente diferente do que uma função síncrona. Em vez de aguardar o resultado de suas dependências (geralmente algumas E / S), ele cria uma tarefa a ser tratada pelo loop do evento principal. Esta não é uma diferença que pode ser bem escondida sob abstração.
fonte
async
métodos em C # para fornecer contratos síncronos também, se desejar. A única diferença é que Go é assíncrono por padrão, enquanto C # é síncrono por padrão.async
fornece o segundo modelo de programação -async
é a abstração (o que realmente faz depende do tempo de execução, agendador de tarefas, contexto de sincronização, implementação do garçom ...).Um método assíncrono se comporta de maneira diferente de um método síncrono, como tenho certeza de que você está ciente. Em tempo de execução, converter uma chamada assíncrona em síncrona é trivial, mas o contrário não pode ser dito. Portanto, a lógica se torna: por que não criamos métodos assíncronos de todos os métodos que podem exigir isso e deixamos o chamador "converter" conforme necessário para um método síncrono?
Em certo sentido, é como ter um método que gera exceções e outro que é "seguro" e não gera nem mesmo em caso de erro. Em que ponto o codificador está sendo excessivo para fornecer esses métodos que, de outra forma, podem ser convertidos um para o outro?
Nisto existem duas escolas de pensamento: uma é criar vários métodos, cada um chamando outro método possivelmente privado, permitindo a possibilidade de fornecer parâmetros opcionais ou pequenas alterações no comportamento, como ser assíncrono. A outra é minimizar os métodos de interface para o essencial, deixando ao chamador executar as modificações necessárias.
Se você é da primeira escola, existe uma certa lógica para dedicar uma aula a chamadas síncronas e assíncronas, a fim de evitar duplicar todas as chamadas. A Microsoft tende a favorecer essa escola de pensamento e, por convenção, para permanecer consistente com o estilo preferido pela Microsoft, você também precisa ter uma versão assíncrona, da mesma maneira que as interfaces quase sempre começam com um "eu". Deixe-me enfatizar que não está errado , por si só, porque é melhor manter um estilo consistente em um projeto, em vez de fazê-lo "da maneira certa" e mudar radicalmente o estilo para o desenvolvimento que você adiciona a um projeto.
Dito isto, tenho a tendência de favorecer a segunda escola, que é minimizar os métodos de interface. Se eu acho que um método pode ser chamado de forma assíncrona, o método para mim é assíncrono. O chamador pode decidir se deve ou não esperar a conclusão da tarefa antes de continuar. Se essa interface for uma interface para uma biblioteca, é mais razoável fazê-lo dessa maneira para minimizar o número de métodos que você precisaria para descontinuar ou ajustar. Se a interface for para uso interno no meu projeto, adicionarei um método para todas as chamadas necessárias em todo o projeto para os parâmetros fornecidos e nenhum método "extra" e, mesmo assim, apenas se o comportamento do método ainda não estiver coberto por um método existente.
No entanto, como muitas coisas nesse campo, é amplamente subjetivo. Ambas as abordagens têm seus prós e contras. A Microsoft também iniciou a convenção de adicionar letras indicativas do tipo no início do nome da variável e "m_" para indicar que é um membro, levando a nomes de variáveis como
m_pUser
. Meu argumento é que nem mesmo a Microsoft é infalível e também pode cometer erros.Dito isto, se o seu projeto estiver seguindo esta convenção Async, aconselho você a respeitá-lo e continuar o estilo. E somente quando você tiver um projeto próprio, poderá escrevê-lo da melhor maneira que achar melhor.
fonte
.Wait()
method e like pode causar consequências negativas, e em js, até onde eu sei, não é possível.Promise.resolve(x)
e adicione retornos de chamada, esses retornos de chamada não serão executados imediatamente.Vamos imaginar que existe uma maneira de permitir que você chame funções de forma assíncrona sem alterar sua assinatura.
Isso seria muito legal e ninguém recomendaria que você mudasse seus nomes.
Porém, funções assíncronas reais, não apenas as que aguardam outra função assíncrona, mas o nível mais baixo possuem alguma estrutura específica para sua natureza assíncrona. por exemplo
São essas duas informações que o código de chamada precisa para executar o código de forma assíncrona que as coisas gostam
Task
easync
encapsulam.Há mais de uma maneira de executar funções assíncronas,
async Task
é apenas um padrão incorporado ao compilador por meio de tipos de retorno, para que você não precise vincular manualmente os eventosfonte
Vou abordar o ponto principal de uma maneira menos C # ness e mais genérica:
Eu diria que isso depende apenas da escolha que você faz no design da sua API e do que você deixa para o usuário.
Se você deseja que uma função da sua API seja assíncrona, há pouco interesse em seguir a convenção de nomenclatura. Apenas sempre retorne Tarefa <> / Promessa <> / Futuro <> / ... como tipo de retorno, pois é auto-documentado. Se quiser uma resposta de sincronização, ele ainda poderá fazer isso aguardando, mas se ele sempre fizer isso, será um pouco complicado.
No entanto, se você fizer apenas a sincronização da API, isso significa que, se um usuário quiser que ela seja assíncrona, ele precisará gerenciar a parte assíncrona dela mesmo.
Isso pode gerar bastante trabalho extra, no entanto, também pode dar mais controle ao usuário sobre quantas chamadas simultâneas ele permite, colocar tempo limite, novas tentativas e assim por diante.
Em um sistema grande com uma API enorme, implementar a maioria deles para sincronização por padrão pode ser mais fácil e mais eficiente do que gerenciar independentemente cada parte da API, especialmente se eles compartilharem recursos (sistema de arquivos, CPU, banco de dados, ...).
De fato, para as partes mais complexas, você poderia perfeitamente ter duas implementações da mesma parte de sua API, uma síncrona fazendo as coisas úteis, uma assíncrona confiando na síncrona, que lida com as coisas e gerencia apenas simultaneidade, cargas, tempos limite e novas tentativas. .
Talvez alguém possa compartilhar sua experiência com isso porque me falta experiência com esses sistemas.
fonte