async & wait - sondage pour des alternatives [clos]

15

Maintenant que nous savons ce qui est en réserve pour le c # 5, il y a apparemment encore une ouverture pour nous pour influencer le choix des deux nouveaux mots clés pour ' Asynchrony ' qui ont été annoncés par Anders Heijsberg hier au PDC10 .

async void ArchiveDocuments(List<Url> urls) {
    Task archive = null;
    for(int i = 0; i < urls.Count; ++i) {
        var document = await FetchAsync(urls[i]);
        if (archive != null)
            await archive;
        archive = ArchiveAsync(document);
    }
}

Eric Lippert a une explication du choix des deux mots clés actuels et de la façon dont ils ont été mal compris dans les études d'utilisabilité. Les commentaires ont plusieurs autres propositions.

S'il vous plaît - une suggestion par réponse, les doublons seront supprimés.

Benjol
la source
D'ailleurs, la «programmation asynchrone intégrée au langage» nous donne LIAP, ne déroule pas tout à fait de la même manière que LINQ;)
Benjol
1
A moins que son saut ne soit prononcé.
Conrad Frix
3
Mais les acronymes "Language-Integrated Asynchronous Runtime" fonctionnent bien.
glenatron
Cela doit être hors sujet.
DeadMG

Réponses:

6

Étant donné que je ne suis pas clair sur le sens / la nécessité de async, je ne peux pas vraiment m'y opposer, mais ma meilleure suggestion pour le remplacer awaitest:

yield while (regardez! pas de nouveaux mots-clés)

Notez qu'après y avoir réfléchi un peu plus, je me demande si la réutilisation whilede cette manière est une bonne idée - la tendance naturelle serait d'attendre un booléen après.

(Pense: trouver de bons mots clés, c'est comme trouver de bons noms de domaine :)

Benjol
la source
+1 et au fait, vous m'avez battu à commenter son entrée de blog de 7 minutes ...
Note à soi-même - pensez à un nom
Mais vous ne cédez pas nécessairement l'exécution si la tâche est déjà terminée. Mais vous attendez toujours la fin de la tâche (mais n'attendez jamais).
Allon Guralnek
@Allon Vous n'exécutez pas nécessairement le corps de la boucle while(x) {...}non plus, si xest faux.
Note à soi-même - pensez à un nom
@Note: Eh bien, il n'y a pas de verbe while. Si vous ajoutez un verbe, comme do, alors vous obtenez do {...} while (x), ce qui exécute le corps indépendamment de x (au moins une fois). Votre suggestion de yield whilesemble très similaire à do while, mais avec des garanties opposées d'exécuter le verbe, ce qui pourrait être un peu trompeur (mais pas beaucoup). Ce qui me déplaît le plus, yieldc'est que cela implique la mise en place d'un mécanisme. L'intérêt de async/ awaitest que vous écrivez une opération asynchrone dans un style synchrone. yieldrompt ce style synchrone.
Allon Guralnek
Un nouveau mot-clé est-il nécessairement une mauvaise chose? Si je comprends bien, le awaitmot - clé serait reconnu par le contexte, vous pouvez donc toujours avoir une méthode ou une variable nommée "attendre" si vous le souhaitez. Dans une certaine mesure, je pense que l'utilisation d'un nouveau mot clé pour de nouvelles fonctionnalités est moins déroutante que la réutilisation d'un mot clé existant pour signifier plus d'une chose. (exemple exagéré: dangermouse.net/esoteric/ook.html )
Tim Goodman
5

Que diriez-vous de ne pas avoir de mot-clé?

J'aimerais que le compilateur se rende compte que, la plupart du temps, lorsque j'appelle une méthode asynchrone, je veux le résultat.

Document doc = DownloadDocumentAsync();

C'est ça. La raison pour laquelle les gens ont du mal à trouver un mot-clé pour cette chose est parce que c'est comme avoir un mot-clé pour "faire ce que vous feriez si les choses étaient parfaitement normales". Cela devrait être la valeur par défaut, pas besoin d'un mot clé.

Mise à jour

J'ai suggéré à l'origine que le compilateur devrait être intelligent avec l'inférence de type pour comprendre quoi faire. En réfléchissant davantage à cela, je garderais l'implémentation existante dans le CTP telle qu'elle est, mais j'y ferais quelques ajouts triviaux, afin de réduire les cas où vous auriez besoin d'utiliser le awaitmot clé explicitement.

Nous inventons un attribut: [AutoAwait]. Cela ne peut être appliqué qu'aux méthodes. Une façon de l'appliquer à votre méthode consiste à la marquer async. Mais vous pouvez également le faire à la main, par exemple:

[AutoAwait]
public Task<Document> DownloadDocumentAsync()

Ensuite, à l'intérieur de n'importe quelle asyncméthode, le compilateur supposera que vous voulez attendre un appel à DownloadDocumentAsync, vous n'avez donc pas besoin de le spécifier. Tout appel à cette méthode l'attendra automatiquement.

Document doc = DownloadDocumentAsync();

Maintenant, si vous voulez "devenir intelligent" et obtenir le Task<Document>, vous utilisez un opérateur start, qui ne peut apparaître qu'avant un appel de méthode:

Task<Document> task = start DownloadDocumentAsync();

Bien, je pense. Maintenant, un appel de méthode simple signifie ce qu'il signifie généralement: attendez que la méthode soit terminée. Et startindique quelque chose de différent: n'attendez pas.

Pour le code qui apparaît en dehors d'une asyncméthode, la seule façon dont vous êtes autorisé à appeler une [AutoAwait]méthode est de la préfixer avec start. Cela vous oblige à écrire du code qui a la même signification, qu'il apparaisse asyncou non dans une méthode.

Ensuite, je commence à devenir gourmand! :)

Tout d'abord, je souhaite asyncappliquer aux méthodes d'interface:

interface IThing
{
    async int GetCount();
} 

Cela signifie essentiellement que la méthode d'implémentation doit retourner Task<int>ou quelque chose de compatible avec await, et les appelants à la méthode obtiendront un [AutoAwait]comportement.

De plus, lorsque j'implémente la méthode ci-dessus, je veux pouvoir écrire:

async int GetCount()

Je n'ai donc pas à mentionner Task<int>comme type de retour.

De plus, je veux asyncm'appliquer aux types de délégués (qui, après tout, sont comme des interfaces avec une méthode). Donc:

public async delegate TResult AsyncFunc<TResult>();

Un asyncdélégué a - vous l'aurez deviné - un [AutoAwait]comportement. À partir d'une asyncméthode, vous pouvez l'appeler et elle sera automatiquement awaitéditée (à moins que vous ne choisissiez de la juste start). Et donc si vous dites:

AsyncFunc<Document> getDoc = DownloadDocumentAsync;

Ça marche. Ce n'est pas un appel de méthode. Aucune tâche n'a encore été lancée - une async delegaten'est pas une tâche. C'est une usine pour faire des tâches. Tu peux dire:

Document doc = getDoc();

Et cela va démarrer une tâche et attendre qu'elle se termine et vous donner le résultat. Ou vous pouvez dire:

Task<Document> t = start getDoc();

Donc, un endroit dans ce domaine où la "plomberie" fuit est que si vous voulez faire un délégué à une asyncméthode, vous devez savoir utiliser un async delegatetype. Donc au lieu de Funcvous devez dire AsyncFunc, et ainsi de suite. Bien qu'un jour ce genre de chose pourrait être corrigé par une inférence de type améliorée.

Une autre question est de savoir ce qui devrait arriver si vous dites commencer par une méthode ordinaire (non asynchrone). De toute évidence, une erreur de compilation serait l'option sûre. Mais il y a d'autres possibilités.

Daniel Earwicker
la source
Cela pourrait être faisable avec une conversion implicite mais nécessiterait sinon une évaluation de gauche à droite de l'instruction (ce qui est exactement le contraire de la façon dont le compilateur fonctionne normalement, sauf pour les lambdas). Je pense que je serais toujours contre cela car cela empêche l'utilisation de var, potentiellement devoir remplacer un nom de type long et explicite, et est également ambigu entre le awaitcas et le cas où quelqu'un a accidentellement appelé la méthode async au lieu de la méthode synchrone normale. Cela semble intuitif au premier abord mais viole en fait le principe de la moindre surprise.
Aaronaught,
@Aaronaught - Pourquoi empêche-t-il l'utilisation de var? Je me demande si vous répondez à la révision précédente de ma réponse ... Je l'ai complètement réécrite. Vous pouvez maintenant penser à cette suggestion comme suit: si la méthode est marquée avec un attribut spécial, c'est comme si le awaitmot-clé était automatiquement inséré devant les appels à cette méthode (à moins que vous ne le supprimiez avec le startpréfixe). Tout reste exactement comme dans le CTP, et varfonctionne donc très bien.
Daniel Earwicker
En effet, j'étais ... étrange que j'ai décidé de revoir ce fil et de répondre à votre réponse presque exactement au même moment où vous avez décidé de le modifier. Je vais devoir le relire maintenant ...
Aaronaught
1
J'aime votre inversion du mot clé wait. Et je n'aime pas non plus la triple redondance de public async Task<int> FooAsync().
Allon Guralnek
1
Oui, je vois cette convention de dénomination Async-postfix comme un signe que quelque chose pourrait être capturé de manière plus formelle. Fondamentalement, s'il y a une règle disant "les méthodes comme celle-ci doivent être nommées d'une certaine manière, afin que les gens sachent comment les appeler correctement", il s'ensuit que la même règle peut être utilisée pour attribuer ces méthodes d'une certaine manière, puis le compilateur pour vous aider à les appeler correctement.
Daniel Earwicker
4
hearken unto AsyncFetch(…)

(si vous ne l'obtenez pas, lisez l' entrée de blog d'Eric . Au moins c'est mieux que for sooth Romeo wherefore art thou AsyncFetch(…))

Note à soi - pense à un nom
la source
(ceux qui n'ont pas le sens de l'humour n'ont pas besoin de postuler)
Note à soi-même - pensez à un nom
4

Je pense que ça asyncva, mais c'est peut-être parce que je l'associe aux pages asynchrones ASP.NET - même idée.

Pour le awaitmot - clé que je préfère continue afterou resume after.

Je ne suis pas comme yieldou l' une de ses variantes, parce que la sémantique sont telles que la méthode peut donner jamais réellement l' exécution; cela dépend de l'état de la tâche.

Aaronaught
la source
J'aime resume afterpour await. Peut async-être qu'on pourrait l'appeler resumable.
Tim Goodman
Bien que je préfère juste after, l' continue afterapproche a un fort avantage d'implémentation: elle inclut un mot-clé contextuel existant, mais avec une syntaxe qui n'est pas compatible avec l'utilisation actuelle. Cela garantit que l'ajout ne cassera jamais le code existant. Lorsque vous utilisez un mot clé entièrement nouveau, l'implémentation doit faire face aux utilisations possibles du mot comme identifiant sur un code plus ancien, ce qui peut devenir assez délicat.
Edurne Pascual
3

J'ai également ajouté des commentaires sur le blog d'Eric, je ne vois aucun problème avec l'utilisation du même mot clé async

var data = async DownloadFileAsync(url);

J'exprime juste que je veux télécharger le fichier de manière asynchrone. Il y a un peu de redondance ici, "async" apparaît deux fois, car c'est aussi dans le nom de la méthode. Le compilateur pourrait être très intelligent et détecter la convention selon laquelle les méthodes se terminant par "Async" sont en fait des méthodes async, et ajouter cela pour nous dans le code compilé. Au lieu de cela, vous voudrez peut-être juste appeler

var data = async DownloadFile(url);

au lieu d'appeler le synchrone

var data = DownloadFile(url);

Heck, nous devrions également être en mesure de les définir de la même manière, puisque le mot-clé async est là dans notre déclaration, pourquoi devons-nous ajouter manuellement "Async" à chaque nom de méthode - le compilateur peut le faire pour nous.

Mark H
la source
J'aime le sucre que vous avez ajouté, bien que les gars C # n'iront probablement pas pour cela. (FWIW, ils font déjà quelque chose de similaire lors de la recherche de noms d'attribut)
Note à soi-même - pensez à un nom
2
Et étant donné que le asyncmot - clé sur la méthode est juste une subtilité (si j'ai bien compris), je me demande si la meilleure chose ne serait pas de faire le contraire de ce que vous proposez: abandonner le asyncsur la méthode, et simplement l'utiliser où ils ont actuellement await.
Benjol
C'est une possibilité. Le mot-clé async sur la méthode delcaration est juste là pour la propreté vraiment. Je préférerais qu'il reste là, mais sans l'exigence pour nous d'ajouter "Async" aux noms de méthode. Par exemple, async Task<Byte[]> DownloadFile(...)plutôt que Task<Byte[]> DownloadFileAsync(...)(Ce dernier sera de toute façon la signature compilée). De toute façon ça marche.
Mark H
Honnêtement, je ne suis pas non plus fan de cela. Comme dans un commentaire précédent, je dois souligner que votre version finale viole le principe de la moindre surprise, car elle invoque en fait une méthode complètement différente de celle écrite, et l'implémentation et la signature de cette méthode sont entièrement à la classe d'implémentation . Même la première version est problématique car elle ne dit vraiment rien (exécuter de manière asynchrone cette méthode asynchrone?). La pensée que nous essayons d'exprimer est une poursuite ou une exécution différée et cela n'exprime rien du tout.
Aaronaught
3

async = task - Il s'agit de modifier une fonction pour renvoyer une tâche, alors pourquoi ne pas utiliser le mot-clé "task"?

wait = finish - Nous n'avons pas nécessairement besoin d'attendre, mais la tâche doit "terminer" avant d'utiliser le résultat.

John Fisher
la source
Il est vraiment difficile de contester la simplicité ici.
sblom
2

J'aime yield until. yield while, déjà suggéré, est génial et n'introduit pas de nouveaux mots clés, mais je pense que "jusqu'à" capture un peu mieux le comportement.

Je pense que yield <something>c'est une excellente idée, car le rendement capture déjà l'idée de faire si bien le reste de la méthode. Peut-être que quelqu'un peut penser à un meilleur mot que «jusqu'à».

nlawalker
la source
2

Je veux simplement enregistrer mon vote pour la suggestion d'Aaron G comefrom- la première utilisation appropriée que j'ai vue de la déclaration COMCFROM d'INTERCAL . L'idée est que c'est en quelque sorte l'opposé de GOTO (sauter de l'instruction GOTO) en ce sens qu'il fait de la place dans votre code pour passer à l'instruction COMEFROM.

Gabe
la source
2

Puisque nous avons affaire à Task<T>s, que diriez-vous d'utiliser startcomme mot-clé précédant l'instruction, comme dans:

start var document = FetchAsync(urls[i]);

Protagoniste
la source
Hmmm, ce finishserait peut -être encore mieux que start?
Protagoniste
1

Il est à noter que F # utilise également le async mot clé dans ses flux de travail asynchrones, ce qui est à peu près la même chose que la nouvelle fonctionnalité asynchrone en C # 5. Par conséquent, je garderais celui-là même

Pour le awaitmot clé en F #, ils utilisent simplement let!au lieu de let. C # n'a pas la même syntaxe d'affectation, ils ont donc besoin de quelque chose sur le côté droit du =signe. Comme l'a dit Benjol, il fonctionne de la même manière yieldque ce devrait donc être presque une variante de cela.

Scott Whitlock
la source
1
Un "attente" ne doit pas du tout être dans une affectation (bien que ce soit bien sûr généralement le cas.) Il est légal en tant qu'opérateur sur à peu près n'importe quelle expression qui a un type sur lequel nous pouvons trouver un GetAwaiter. (Les règles exactes doivent encore être élaborées sous une forme publiable.)
Eric Lippert
1
@Eric, en F # ce serait do!, mais vous le saviez ...
Benjol
1

yield async FetchAsync(..)


Cela va parfaitement avec le asyncmodificateur que vous devez appliquer à la méthode que vous invoquez. Et aussi la sémantique du courant yield returnqui est, vous revenez et donne l'exécution au code énumérateur tandis que dans ce cas, vous cédez votre exécution à la méthode asynchrone.

Imaginez si à l'avenir il y aurait d'autres utilisations yield, nous pourrions ajouter un yield xoù x est la nouvelle fonctionnalité brillante au lieu d'avoir tous ces mots clés différents pour faire la plupart du temps la même chose, donner l'exécution.

Franchement, je ne comprends pas très bien l'argument «ne pas céder l'exécution». Après tout, l'intérêt d'appeler une autre méthode n'est-il pas déjà de «céder l'exécution» à cette méthode? Qu'il soit asynchrone ou non? Je manque quelque chose ici?

Et bon pour vous si le async retourne de manière synchrone mais en ayant le mot-clé là, cela signifie qu'il y a une chance probable que la méthode s'exécute de manière asynchrone et que vous céderez l'exécution à une autre méthode. Votre méthode doit en tenir compte, qu'elle effectue ou non des appels asynchrones.

OMI Je pense que les différents cas «sans céder» sont un détail de mise en œuvre. Je préfère garantir la cohérence de la langue (c'est-à-dire la réutilisation yield).

chakrit
la source
0

Que diriez-vous complete, comme dans "Je veux que la tâche soit terminée"?

Task<byte[]> downloadTask = DownloadFileAsync(url);
byte[] data = complete downloadTask;
Allon Guralnek
la source
1
Pourquoi le downvote? C'est une courtoisie de vous expliquer au moins après le vote négatif.
Allon Guralnek
0

task(pour la déclaration de méthode) et async(dans le corps de la méthode)

sblom
la source
Cette réponse est un peu tardive, ils se sont installés sur async et attendent il y a un an.
Jay Elston