Brouiller les lignes entre les fonctions asynchrones et régulières dans C # 5.0

10

Dernièrement, je n'arrive pas à en avoir assez de l'incroyable motif asynchrone de C # 5.0. Où étais-tu, durant toute ma vie?

Je suis absolument ravi de la syntaxe simple, mais j'ai une petite difficulté. Mon problème est que les fonctions asynchrones ont une déclaration totalement différente des fonctions régulières. Étant donné que seules les fonctions asynchrones peuvent attendre sur d'autres fonctions asynchrones, lorsque j'essaie de porter un ancien code de blocage vers async, j'ai un effet domino des fonctions que je dois convertir.

Les gens ont appelé cela une infestation de zombies . Lorsque async obtient une morsure dans votre code, il ne cesse de s'agrandir. Le processus de portage n'est pas difficile, il suffit de jeter asyncla déclaration et d'envelopper la valeur de retour avec Task<>. Mais il est ennuyeux de le faire encore et encore lors du portage de l'ancien code synchrone.

Il me semble que ce sera beaucoup plus naturel si les deux types de fonctions (async et plain old sync) avaient exactement la même syntaxe. Si tel était le cas, le portage ne demanderait aucun effort et je pourrais basculer sans douleur entre les deux formes.

Je pense que cela pourrait fonctionner si nous suivons ces règles:

  1. Les fonctions asynchrones ne nécessiteront plus la asyncdéclaration. Leurs types de retour n'auraient pas à être enveloppés Task<>. Le compilateur identifiera lui-même une fonction asynchrone pendant la compilation et effectuera automatiquement l'enveloppement de la tâche <> selon les besoins.

  2. Plus besoin de tirer et d'oublier les fonctions asynchrones. Si vous souhaitez appeler une fonction asynchrone, vous devrez l'attendre. J'utilise à peine le feu et oublie de toute façon, et tous les exemples de conditions de course folles ou de blocages semblent toujours être basés sur eux. Je pense qu'ils sont trop confus et «déconnectés» de l'état d'esprit synchrone que nous essayons de tirer parti.

  3. Si vous ne pouvez vraiment pas vivre sans tirer et oublier, il y aura une syntaxe spéciale pour cela. En tout cas, cela ne fera pas partie de la simple syntaxe unifiée dont je parle.

  4. Le seul mot-clé dont vous avez besoin pour désigner un appel asynchrone est await. Si vous avez attendu, l'appel est asynchrone. Si vous ne le faites pas, l'appel est tout simplement synchrone (rappelez-vous, nous n'avons plus le feu et oublions).

  5. Le compilateur identifiera automatiquement les fonctions asynchrones (car elles n'ont plus de déclaration spéciale). La règle 4 rend cela très simple à faire - si une fonction a un awaitappel à l'intérieur, c'est async.

Cela pourrait-il fonctionner? ou est-ce que je manque quelque chose? Cette syntaxe unifiée est beaucoup plus fluide et pourrait résoudre complètement l'infestation de zombies.

Quelques exemples:

// assume this is an async function (has await calls inside)
int CalcRemoteBalanceAsync() { ... }

// assume this is a regular sync function (has no await calls inside)
int CalcRemoteBalance() { ... }

// now let's try all combinations and see what they do:

// this is the common synchronous case - it will block
int b1 = CalcRemoteBalance();

// this is the common asynchronous case - it will not block
int b2 = await CalcRemoteBalanceAsync();

// choosing to call an asynchronous function in a synchronous manner - it will block
// this syntax was used previously for async fire-and-forget, but now it's just synchronous
int b3 = CalcRemoteBalanceAsync();

// strange combination - it will block since it's calling a synchronous function
// it should probably show a compilation warning though
int b4 = await CalcRemoteBalance();

Remarque: il s'agit d'une continuation d'une discussion connexe intéressante dans SO

talkol
la source
3
Vous attendez toujours vos opérations asynchrones? S'il vous plaît, dites-moi que vous ne faites pas cela immédiatement après les avoir licenciés ...
Jimmy Hoffa
1
De plus, l'un des avantages de l'async est que vous n'avez pas à le faire awaitimmédiatement. Vous pouvez faire quelque chose comme ça var task = FooAsync(); Bar(); await task;. Comment pourrais-je procéder dans votre proposition?
svick
3
SO a une discussion? Où est mon BFG-3000 ...
Robert Harvey
2
@talkol Vous pensez que la programmation parallèle est obscène? C'est pour le moins une perspective intéressante lorsque vous parlez async. Je pense que c'est l'un des grands avantages de async- await: qu'il vous permet de composer facilement des opérations asynchrones (et pas seulement de la manière la plus simple "démarrer A, attendre A, démarrer B, attendre B"). Et il existe déjà une syntaxe spéciale à cet effet: elle s'appelle await.
svick
1
@svick haha, maintenant nous y sommes allés :) Je ne pense pas que le prog parallèle soit obscène, mais je pense que le faire avec async-wait l'est. L'attente asynchrone est un sucre syntaxique pour garder votre état d'esprit synchrone sans payer le prix du blocage. Si vous pensez déjà en parallèle, je vous exhorte à utiliser un modèle différent
talkol

Réponses:

9

Vous avez déjà répondu à votre question dans la question SO que vous avez liée.

Le but de async / wait est de faciliter l'écriture de code dans un monde avec de nombreuses opérations à latence élevée. La grande majorité de vos opérations ne sont pas à latence élevée.

Lorsque WinRT est sorti pour la première fois, les concepteurs décrivaient comment ils décidaient quelles opérations allaient être asynchrones. Ils ont décidé que tout ce qui allait prendre 50 ms ou plus serait asynchrone, et le reste des méthodes serait des méthodes ordinaires, non asynchrones.

Combien de méthodes ont dû être réécrites pour les rendre asynchrones? Environ 10% d'entre eux. Les 90% restants n'ont pas été affectés du tout.

Eric Lippert poursuit en expliquant dans des détails techniques assez substantiels pourquoi ils ont choisi de ne pas adopter une approche unique. Il dit essentiellement que asyncet awaitsont une implémentation partielle du style de passage de continuation, et que l'optimisation d'un tel style pour s'adapter à tous les cas est un problème difficile.

Robert Harvey
la source
Veuillez noter la différence substantielle entre la question SO et celle-ci. Le SO demande pourquoi ne pas tout faire asynchrone. Ici, nous ne suggérons pas cela, nous suggérons de faire 10% d'async, en utilisant simplement la même syntaxe pour tout cela. L'utilisation d'une syntaxe plus étroite a l'avantage que vous pouvez plus facilement changer lequel 10% est asynchrone, sans subir les effets domino de ces changements
talkol
Je ne sais pas très bien pourquoi asyncproduirait des infestations de zombies. Même si une méthode appelle 10 autres méthodes, ne vous contentez-vous pas de asynccelle de niveau supérieur?
Robert Harvey
6
Disons que 100% de mon code actuel est synchronisé. Maintenant, j'ai une seule fonction interne au niveau feuille qui interroge la base de données que je voudrais changer en async. Maintenant, pour le rendre asynchrone, j'ai besoin que son appelant soit asynchrone et que son appelant soit asynchrone, et ainsi de suite jusqu'au niveau supérieur. Bien sûr, je parle du cas où toute la chaîne est attendue (pour conserver la conception synchrone du code ou passer les
valeurs de