Cette question concerne le langage C #, mais je m'attends à ce qu'il couvre d'autres langages tels que Java ou TypeScript.
Microsoft recommande les meilleures pratiques sur l' utilisation des appels asynchrones dans .NET. Parmi ces recommandations, prenons deux:
- modifier la signature des méthodes asynchrones afin qu'elles renvoient la tâche ou la tâche <> (dans TypeScript, ce serait une promesse <>)
- modifier les noms des méthodes asynchrones pour qu'ils se terminent par xxxAsync ()
Désormais, le remplacement d’un composant synchrone de bas niveau par un composant asynchrone a une incidence sur l’ensemble de la pile de l’application. Comme async / wait n’a un impact positif que s’il est utilisé «à tous les niveaux», cela signifie que les noms de signature et de méthode de chaque couche de l’application doivent être modifiés.
Une bonne architecture implique souvent de placer des abstractions entre chaque couche, de sorte que le remplacement des composants de bas niveau par d'autres ne soit pas vu par les composants de niveau supérieur. En C #, les abstractions se présentent sous la forme d'interfaces. Si nous introduisons un nouveau composant asynchrone de bas niveau, chaque interface de la pile d'appels doit être modifiée ou remplacée par une nouvelle interface. La façon dont un problème est résolu (asynchrone ou sync) dans une classe d'implémentation n'est plus cachée (abstraite) pour les appelants. Les appelants doivent savoir si c'est synchrone ou asynchrone.
Est-ce que les meilleures pratiques ne sont pas asynchrones / en contradiction avec les principes de "bonne architecture"?
Cela signifie-t-il que chaque interface (par exemple IEnumerable, IDataAccessLayer) a besoin de son homologue asynchrone (IAsyncEnumerable, IAsyncDataAccessLayer) pour pouvoir être remplacée dans la pile lors du basculement vers des dépendances asynchrones?
Si nous poussons le problème un peu plus loin, ne serait-il pas plus simple de supposer que chaque méthode est asynchrone (pour renvoyer une tâche <> ou une promesse <>), et que les méthodes synchronisent les appels asynchrones lorsqu'ils ne sont pas réellement Async? Est-ce quelque chose à attendre des futurs langages de programmation?
la source
CancellationToken
, et celles qui le font peuvent souhaiter fournir une valeur par défaut). Supprimer les méthodes de synchronisation existantes (et briser de manière proactive tout le code) est une évidence.Réponses:
De quelle couleur est votre fonction?
Vous pouvez être intéressé par Quelle couleur est votre fonction de Bob Nystrom 1 .
Dans cet article, il décrit une langue de fiction dans laquelle:
Bien que fictif, cela se produit assez régulièrement dans les langages de programmation:
this
.Comme vous l'avez compris, à cause de ces règles, les fonctions rouges ont tendance à se répandre dans la base de code. Vous en insérez un et petit à petit, il colonise la base de code entière.
1 Bob Nystrom, hormis les blogs, fait également partie de l'équipe Dart et a écrit cette petite série de Crafting Interpreters; hautement recommandé pour tout amateur de langage de programmation / compilateur.
2 Ce n'est pas tout à fait vrai, car vous pouvez appeler une fonction asynchrone et la bloquer jusqu'à ce qu'elle revienne, mais ...
Limite de langue
Il s’agit essentiellement d’une limitation langue / exécution.
Les langues avec un filetage M: N, par exemple Erlang et Go, n'ont pas de
async
fonctions: chaque fonction est potentiellement asynchrone et sa "fibre" est simplement suspendue, remplacée et replacée lorsqu'elle est prête à nouveau.C # a opté pour un modèle de thread 1: 1 et a donc décidé de faire apparaître la synchronicité dans le langage afin d’éviter tout blocage accidentel des threads.
En présence de limitations linguistiques, les directives de codage doivent être adaptées.
la source
async
); En effet, il s'agissait d'un modèle assez courant pour la création d'une interface utilisateur réactive. Était-ce aussi joli qu'Erlang? Nan. Mais c'est encore loin de C :)Vous avez raison, il y a une contradiction ici, mais ce ne sont pas les "meilleures pratiques" qui sont mauvaises. C'est parce que la fonction asynchrone fait essentiellement autre chose qu'une fonction synchrone. Au lieu d'attendre le résultat de ses dépendances (généralement quelques E / S), il crée une tâche à gérer par la boucle d'événements principale. Ce n'est pas une différence qui peut être bien cachée sous abstraction.
la source
async
méthodes en C # pour fournir des contrats synchrones, si vous le souhaitez. La seule différence est que Go est asynchrone par défaut alors que C # est synchrone par défaut.async
vous donne le deuxième modèle de programmation -async
est l'abstraction (ce qu'il fait réellement dépend de l'exécution, du planificateur de tâches, du contexte de synchronisation, de la mise en œuvre de l'attente ...).Une méthode asynchrone se comporte différemment d'une méthode synchrone, comme vous le savez sûrement. Au moment de l'exécution, convertir un appel asynchrone en un appel synchrone est simple, mais l'inverse ne peut pas être dit. La logique devient alors logique. Pourquoi ne faisons-nous pas des méthodes asynchrones de toutes les méthodes qui peuvent en avoir besoin et laissons-nous "convertir" au besoin en méthode synchrone?
En un sens, c'est comme avoir une méthode qui jette des exceptions et une autre qui est "sûre" et qui ne le sera pas, même en cas d'erreur. À quel moment le codeur est-il excessif pour fournir ces méthodes qui peuvent sinon être converties les unes aux autres?
Il existe deux écoles de pensée dans ce domaine. L’une consiste à créer plusieurs méthodes, chacune appelant une autre méthode, éventuellement privée, offrant la possibilité de fournir des paramètres facultatifs ou des modifications mineures au comportement, telles que l’asynchrone. L’autre consiste à minimiser les méthodes d’interface au strict minimum, en laissant à l’appelant le soin d’apporter lui-même les modifications nécessaires.
Si vous êtes de la première école, il y a une certaine logique à consacrer une classe aux appels synchrones et asynchrones afin d'éviter de doubler chaque appel. Microsoft a tendance à privilégier cette école de pensée et, par convention, à rester cohérent avec le style préconisé par Microsoft, vous aussi devez disposer d'une version asynchrone, de la même manière que les interfaces commencent presque toujours par un "I". Permettez-moi de souligner que ce n'est pas faux en soi, car il est préférable de conserver un style cohérent dans un projet plutôt que de le faire "de la bonne manière" et de changer radicalement de style pour le développement que vous ajoutez à un projet.
Cela dit, j'ai tendance à privilégier la deuxième école, qui consiste à minimiser les méthodes d'interface. Si je pense qu'une méthode peut être appelée de manière asynchrone, la méthode pour moi est asynchrone. L'appelant peut décider d'attendre ou non la fin de la tâche avant de poursuivre. Si cette interface est une interface pour une bibliothèque, il est plus raisonnable de le faire de cette manière afin de réduire le nombre de méthodes à déprécier ou à ajuster. Si l'interface est à usage interne dans mon projet, j'ajouterai une méthode pour chaque appel nécessaire tout au long de mon projet pour les paramètres fournis, sans aucune méthode "extra", et ce, même si le comportement de la méthode n'est pas déjà couvert. par une méthode existante.
Cependant, comme beaucoup de choses dans ce domaine, c'est en grande partie subjectif. Les deux approches ont leurs avantages et leurs inconvénients. Microsoft a également lancé la convention consistant à ajouter des lettres indiquant le type au début du nom de la variable, ainsi que "m_" pour indiquer qu'il s'agit d'un membre menant à des noms de variable tels que
m_pUser
. Mon argument étant que même Microsoft n'est pas infaillible et peut aussi commettre des erreurs.Cela dit, si votre projet suit cette convention asynchrone, je vous conseillerais de la respecter et de continuer le style. Et seulement une fois que vous avez un projet à vous, vous pouvez l'écrire de la manière qui vous convient le mieux.
la source
.Wait()
method et similaires peut avoir des conséquences négatives, et en js, autant que je sache, ce n’est pas du tout possible.Promise.resolve(x)
puis ajoutez des rappels, ces rappels ne seront pas exécutés immédiatement.Imaginons qu'il existe un moyen de vous permettre d'appeler des fonctions de manière asynchrone sans changer leur signature.
Ce serait vraiment cool et personne ne vous recommanderait de changer leurs noms.
Cependant, les fonctions asynchrones réelles, pas seulement celles qui attendent une autre fonction asynchrone, mais le niveau le plus bas ont une structure spécifique à leur nature asynchrone. par exemple
Il est ces deux bits d'information que le code d'appel a besoin pour exécuter le code de manière async que des choses comme
Task
etasync
encapsuler.Il y a plus d'une façon de faire des fonctions asynchrones,
async Task
c'est juste un modèle intégré dans le compilateur via des types de retour afin que vous n'ayez pas à lier manuellement les événementsla source
J'aborderai le point principal d'une manière moins générique et plus générique:
Je dirais que cela dépend du choix que vous faites dans la conception de votre API et de ce que vous laissez à l'utilisateur.
Si vous souhaitez qu'une fonction de votre API soit uniquement asynchrone, il est peu intéressant de suivre la convention de dénomination. Il suffit simplement de toujours renvoyer la tâche <> / Promise <> / Future <> / ... en tant que type de retour, elle est auto-documentée. S'il veut une réponse synchronisée, il pourra toujours le faire en attendant, mais s'il le fait toujours, cela fera un peu casse-tête.
Toutefois, si vous ne synchronisez que votre API, cela signifie que si un utilisateur souhaite le rendre asynchrone, il devra gérer lui-même la partie asynchrone.
Cela peut nécessiter beaucoup de travail supplémentaire, mais cela peut également donner plus de contrôle à l'utilisateur sur le nombre d'appels simultanés qu'il autorise, la mise en attente, les tentatives de répétition, etc.
Dans un grand système avec une API volumineuse, implémenter la plupart d’entre elles pour être synchronisées par défaut pourrait être plus simple et plus efficace que de gérer indépendamment chaque partie de votre API, en particulier si elles partagent des ressources (système de fichiers, CPU, base de données, ...).
En fait, pour les parties les plus complexes, vous pouvez parfaitement avoir deux implémentations de la même partie de votre API: une tâche synchrone, une tâche asynchrone, une version asynchrone reposant sur la tâche synchrone qui gère les tâches et ne gère que les accès simultanés, les chargements, les délais impartis et les nouvelles tentatives. .
Peut-être que quelqu'un d'autre peut partager son expérience avec cela parce que je manque d'expérience avec de tels systèmes.
la source