51 votes

Confusion concernant les threads et si les méthodes asynchrones sont vraiment asynchrones en C#

J'étais en train de lire sur async/await et quand Task.Yield pourrait être utile et je suis tombé sur ce poste. J'avais une question concernant l'extrait ci-dessous de ce post :

Lorsque vous utilisez async/await, il n'y a aucune garantie que la méthode que vous appelle lorsque vous faites attendre FooAsync() s'exécutera en fait de manière asynchrone. L'implémentation interne est libre de revenir en utilisant un chemin complètement complètement synchrone.

C'est un peu flou pour moi, probablement parce que la définition d'asynchrone dans ma tête ne correspond pas.

Dans mon esprit, puisque je fais principalement du développement d'interface utilisateur, le code asynchrone est un code qui ne s'exécute pas sur le thread de l'interface utilisateur, mais sur un autre thread. Je suppose que dans le texte que j'ai cité, une méthode n'est pas vraiment asynchrone si elle bloque sur n'importe quel thread (même si c'est un thread pool par exemple).

Pregunta:

Si j'ai une tâche qui s'exécute longtemps et qui est liée au CPU (disons qu'elle fait beaucoup de calculs difficiles), alors l'exécution asynchrone de cette tâche doit bloquer un thread, n'est-ce pas ? Quelque chose doit en effet effectuer les calculs. Si je l'attends, un thread est bloqué.

Quel est l'exemple d'une méthode véritablement asynchrone et comment fonctionne-t-elle réellement ? Sont-elles limitées aux opérations d'E/S qui tirent parti de certaines capacités matérielles, de sorte qu'aucun thread n'est jamais bloqué ?

22 votes

0 votes

J'aime votre question, mais le titre "Méthodes asynchrones en .Net/C#" est trop général.

0 votes

@Julian J'ai mis à jour la question de l'OP pour être plus spécifique.

112voto

Eric Lippert Points 300275

C'est un peu flou pour moi, probablement parce que la définition d'asynchrone dans ma tête ne correspond pas.

C'est bien que vous demandiez une clarification.

Dans mon esprit, puisque je fais principalement du développement d'interface utilisateur, le code asynchrone est un code qui ne s'exécute pas sur le thread de l'interface utilisateur, mais sur un autre thread.

Cette croyance est courante mais fausse. Il n'est pas nécessaire que le code asynchrone s'exécute sur un deuxième thread.

Imaginez que vous êtes en train de préparer le petit-déjeuner. Vous mettez des toasts dans le grille-pain, et pendant que vous attendez que les toasts éclatent, vous regardez votre courrier d'hier, vous payez quelques factures, et voilà que les toasts apparaissent. Vous finissez de payer cette facture, puis vous allez beurrer votre toast.

Où avez-vous engagé un second travailleur pour surveiller votre grille-pain ?

Tu ne l'as pas fait. Les threads sont des travailleurs. Les flux de travail asynchrones peuvent se dérouler sur un seul fil. Le but du flux de travail asynchrone est de éviter d'embaucher plus de travailleurs si vous pouvez l'éviter.

Si j'ai une tâche qui s'exécute longtemps et qui est liée au CPU (disons qu'elle fait beaucoup de calculs difficiles), alors l'exécution asynchrone de cette tâche doit bloquer un thread, n'est-ce pas ? Quelque chose doit en effet effectuer les calculs.

Ici, je vais te donner un problème difficile à résoudre. Voici une colonne de 100 nombres ; veuillez les additionner à la main. Donc vous ajoutez le premier au deuxième et faites un total. Puis vous ajoutez le total courant au troisième et vous obtenez un total. Puis, oh, bon sang, il manque la deuxième page de chiffres. Rappelle-toi où tu en étais, et va faire des toasts. Oh, pendant que le toast grillait, une lettre est arrivée avec les chiffres restants. Quand vous aurez fini de beurrer le pain grillé, continuez à additionner ces chiffres, et n'oubliez pas de manger le pain grillé la prochaine fois que vous aurez un moment de libre.

Où est la partie où vous avez engagé un autre travailleur pour additionner les chiffres ? Les travaux coûteux en calcul ne doivent pas nécessairement être synchrones ni bloquer un thread. . Ce qui rend le travail informatique potentiellement asynchrone, c'est la possibilité de l'arrêter, de se rappeler où l'on était, d'aller faire autre chose, de se rappeler ce qu'il faut faire après cela et reprenez là où vous vous êtes arrêté.

Maintenant, c'est certainement possible d'embaucher un deuxième travailleur qui ne fait rien d'autre qu'ajouter des chiffres, et qui est ensuite licencié. Et vous pourriez demander à cet ouvrier "avez-vous fini ?" et si la réponse est non, vous pourriez aller faire un sandwich jusqu'à ce qu'il ait fini. De cette façon, vous et l'ouvrier êtes tous deux occupés. Mais il n'y a pas de exigence que l'asynchronie implique plusieurs travailleurs.

Si je l'attends, c'est qu'un fil est bloqué.

NON NON NON. C'est la partie la plus importante de votre malentendu. await ne signifie pas "lancer cette tâche de manière asynchrone". await signifie "J'ai ici un résultat produit de manière asynchrone qui pourrait ne pas être disponible. S'il n'est pas disponible, trouver un autre travail à faire sur ce fil afin que nous soyons pas bloquer le fil. Await est le en face de de ce que vous venez de dire.

Quel est l'exemple d'une méthode véritablement asynchrone et comment fonctionne-t-elle réellement ? Sont-elles limitées aux opérations d'E/S qui tirent parti de certaines capacités matérielles, de sorte qu'aucun thread n'est jamais bloqué ?

Le travail asynchrone implique souvent du matériel personnalisé ou des threads multiples, mais ce n'est pas nécessaire.

Ne pensez pas à travailleurs . Pensez à flux de travail . L'essence de l'asynchronie est décomposer les flux de travail en petites parties de telle sorte que vous pouvez déterminer l'ordre dans lequel ces parties doivent se produire. et ensuite en exécutant chaque partie à tour de rôle mais permettre l'intercalation de pièces qui ne dépendent pas les unes des autres .

Dans un flux de travail asynchrone, vous pouvez facilement détecter les endroits du flux de travail où une dépendance entre les parties est exprimée . Ces pièces sont marquées par await . C'est le sens de await Le code qui suit dépend de l'achèvement de cette partie du flux de travail, donc si elle n'est pas achevée, allez chercher une autre tâche à faire, et revenez ici plus tard lorsque la tâche sera achevée. L'objectif est de faire en sorte que le travailleur continue à travailler, même dans un monde où les résultats nécessaires sont produits dans le futur.

12 votes

Je trouve légèrement amusant le fait que nous ayons créé cette abstraction de threads sur le concept de "faire des choses" (comme les interruptions et les DPCs), et que maintenant nous ayons construit une abstraction de "faire des choses" basée sur les threads.

2 votes

@immibis En quoi les interruptions et les DPCs sont-ils une abstraction ? Ce sont des outils, pas des abstractions. Task est une abstraction.

6 votes

Pour l'instant, JavaScript est à la fois monofil et asynchrone.

26voto

Stephen Cleary Points 91731

J'ai lu des articles sur async/await.

Puis-je recommander mon async intro ?

et quand Task.Yield peut être utile

Presque jamais. Je le trouve occasionnellement utile lors des tests unitaires.

Dans mon esprit, puisque je fais principalement du développement d'interface utilisateur, le code asynchrone est un code qui ne s'exécute pas sur le thread de l'interface utilisateur, mais sur un autre thread.

Le code asynchrone peut être sans fil .

Je suppose que dans le texte que j'ai cité, une méthode n'est pas vraiment asynchrone si elle bloque sur n'importe quel thread (même si c'est un thread pool par exemple).

Je dirais que c'est correct. J'utilise le terme "vraiment asynchrone" pour les opérations qui ne bloquent aucun thread (et qui ne sont pas synchrones). J'utilise également le terme "faux asynchrone" pour les opérations qui apparaître asynchrones mais ne fonctionnent que de cette manière parce qu'ils s'exécutent ou bloquent un thread de pool.

Si j'ai une tâche qui s'exécute sur une longue durée et qui est liée au processeur (disons qu'elle fait beaucoup de calculs difficiles), alors l'exécution asynchrone de cette tâche doit bloquer un thread, n'est-ce pas ? Quelque chose doit effectivement faire les calculs.

Oui ; dans ce cas, vous voudriez définir ce travail avec une API synchrone (puisqu'il s'agit d'un travail synchrone), et vous pouvez ensuite l'appeler à partir de votre thread d'interface utilisateur en utilisant la commande Task.Run par exemple :

var result = await Task.Run(() => MySynchronousCpuBoundCode());

Si je l'attends, c'est qu'un fil est bloqué.

Non ; le thread du pool de threads serait utilisé pour exécuter le code (pas réellement bloqué), et le thread de l'interface utilisateur attend de manière asynchrone que ce code soit terminé (également non bloqué).

Quel est un exemple de méthode véritablement asynchrone et comment fonctionne-t-elle réellement ?

NetworkStream.WriteAsync (indirectement) demande à la carte réseau d'écrire quelques octets. Aucun thread n'est chargé d'écrire les octets un par un et d'attendre que chaque octet soit écrit. La carte réseau s'occupe de tout cela. Lorsque la carte réseau a fini d'écrire tous les octets, elle termine (éventuellement) la tâche renvoyée par la commande WriteAsync .

Sont-ils limités aux opérations d'E/S qui tirent parti de certaines capacités matérielles, de sorte qu'aucun thread n'est jamais bloqué ?

Pas entièrement, bien que les opérations d'E/S soient les exemples les plus faciles. Un autre exemple assez facile est celui des minuteries (par ex, Task.Delay ). Mais vous pouvez construire une véritable API asynchrone autour de n'importe quel type d'"événement".

0 votes

Je pense que Task.Yield peut être utile pour empêcher efficacement l'inlining d'une tâche attendue. Si vous voulez que la tâche attendue s'exécute définitivement après le retour de la méthode (async) actuelle, alors attendre Task.Yield à l'intérieur de la tâche attendue poussera effectivement la tâche attendue plus bas dans le planificateur (c'est-à-dire pas inline). -- Oui ?

0 votes

@sjb-sjb : Task.Yield force l'asynchronie, oui. Mais quand avez-vous besoin de le faire ? Presque jamais. C'est occasionnellement utile lors des tests unitaires. Si vous trouvez que vous en avez besoin dans votre code de production, alors regardez longuement et sérieusement la conception de votre code de production et voyez quelles améliorations peuvent être apportées.

0 votes

Oui, je suis d'accord en général, mais ça arrive quand même. Je l'ai rencontré récemment dans une situation trop compliquée pour être expliquée ici mais je dirais que la conception était valide. Bien que l'on puisse normalement utiliser Run pour obtenir l'asynchronie requise, je ne pouvais pas le faire dans cette situation car cela devait être fait sur le thread (UI) actuel. J'ai également vu d'autres situations dans lesquelles des choses se produisaient dans le mauvais ordre sur le thread de l'interface utilisateur et pouvaient être corrigées en utilisant Yield.

10voto

Cory Nelson Points 10540

Lorsque vous utilisez async/await, il n'y a aucune garantie que la méthode que vous appelez lorsque vous faites await FooAsync() s'exécutera réellement de manière asynchrone. L'implémentation interne est libre de retourner en utilisant un chemin complètement synchrone.

C'est un peu flou pour moi, probablement parce que la définition de asynchrone dans ma tête ne concorde pas.

Cela signifie simplement qu'il y a deux cas de figure lorsqu'on appelle une méthode asynchrone.

La première est que, lorsque la tâche vous est retournée, l'opération est déjà terminée - il s'agirait d'un parcours synchrone. La seconde est que l'opération est toujours en cours - c'est la voie asynchrone.

Considérez ce code, qui devrait montrer ces deux chemins. Si la clé est dans un cache, elle est retournée de manière synchrone. Sinon, une opération asynchrone est lancée qui fait appel à une base de données :

Task<T> GetCachedDataAsync(string key)
{
    if(cache.TryGetvalue(key, out T value))
    {
        return Task.FromResult(value); // synchronous: no awaits here.
    }

    // start a fully async op.
    return GetDataImpl();

    async Task<T> GetDataImpl()
    {
        value = await database.GetValueAsync(key);
        cache[key] = value;
        return value;
    }
}

Donc en comprenant cela, vous pouvez déduire qu'en théorie l'appel de database.GetValueAsync() peut avoir un code similaire et lui-même être capable de retourner de manière synchrone : ainsi, même votre Le chemin asynchrone peut finir par fonctionner de manière 100% synchrone. Mais votre code n'a pas besoin de s'en soucier : async/await gère les deux cas de manière transparente.

Si j'ai une tâche qui s'exécute sur une longue durée et qui est liée au processeur (disons qu'elle fait beaucoup de calculs difficiles), l'exécution asynchrone de cette tâche doit bloquer un thread, n'est-ce pas ? Quelque chose doit en effet effectuer les calculs. Si je l'attends, un thread est bloqué.

Le blocage est un terme bien défini. Il signifie que votre thread a cédé sa fenêtre d'exécution pendant qu'il attend quelque chose (E/S, mutex, etc.). Ainsi, votre thread qui fait des maths n'est pas considéré comme bloqué : il est en train de travailler.

Quel est un exemple de méthode véritablement asynchrone et comment fonctionne-t-elle réellement ? Sont-elles limitées aux opérations d'E/S qui tirent parti de certaines capacités matérielles, de sorte qu'aucun thread n'est jamais bloqué ?

Une "méthode véritablement asynchrone" est une méthode qui ne bloque jamais. Cela implique généralement des E/S, mais cela peut aussi signifier await dans votre code mathématique lourd lorsque vous voulez utiliser le thread actuel pour autre chose (comme dans le développement de l'interface utilisateur) ou lorsque vous essayez d'introduire le parallélisme :

async Task<double> DoSomethingAsync()
{
    double x = await ReadXFromFile();

    Task<double> a = LongMathCodeA(x);
    Task<double> b = LongMathCodeB(x);

    await Task.WhenAll(a, b);

    return a.Result + b.Result;
}

6voto

Theraot Points 5174

Asynchrone n'implique pas Parallèle

Asynchrone n'implique que la concurrence. En fait, même l'utilisation de threads explicites ne garantit pas qu'ils s'exécuteront simultanément (par exemple, lorsque les threads ont une affinité pour le même noyau unique, ou plus communément lorsqu'il n'y a qu'un seul noyau dans la machine au départ).

Par conséquent, vous ne devez pas vous attendre à ce qu'une opération asynchrone se produise en même temps que quelque chose d'autre. Asynchrone signifie seulement qu'elle se produira, éventuellement à un autre moment ( a (grec) = sans, syn (grec) = ensemble, khronos (grec) = temps. => Asynchrone \= ne se produisant pas au même moment).

Note : L'idée de l'asynchronisme est que lors de l'invocation, vous ne vous souciez pas du moment où le code sera effectivement exécuté. Cela permet au système de tirer parti du parallélisme, si possible, pour exécuter l'opération. Elle peut même être exécutée immédiatement. Cela peut même se produire sur le même thread... nous y reviendrons plus tard.

Quand vous await l'opération asynchrone, vous créez des Concurrence ( com (latin) = ensemble, current (latin) = courir. => "Concurrent" = de courir ensemble ). C'est parce que vous demandez que l'opération asynchrone soit terminée avant de passer à autre chose. On peut dire que l'exécution converge. Ce concept est similaire à celui de la jonction des threads.


Quand l'asynchrone ne peut pas être Parallèle

Lorsque vous utilisez async/await, il n'y a aucune garantie que la méthode que vous appelez lorsque vous faites await FooAsync() s'exécutera réellement de manière asynchrone. L'implémentation interne est libre de retourner en utilisant un chemin complètement synchrone.

Cela peut se produire de trois façons :

  1. Il est possible d'utiliser await sur tout ce qui renvoie Task . Lorsque vous recevez le Task elle pourrait avoir déjà été réalisée.

    Pourtant, cela ne signifie pas pour autant qu'elle a fonctionné de manière synchrone. En fait, cela suggère qu'il s'est exécuté de manière asynchrone et qu'il s'est terminé avant que vous n'obteniez l'icône Task instance.

    Gardez à l'esprit que vous pouvez await sur une tâche déjà accomplie :

    private static async Task CallFooAsync()
    {
        await FooAsync();
    }
    
    private static Task FooAsync()
    {
        return Task.CompletedTask;
    }
    
    private static void Main()
    {
        CallFooAsync().Wait();
    }

    En outre, si un async n'a pas de await il fonctionnera de manière synchrone.

    Remarque : Comme vous le savez déjà, une méthode qui renvoie un fichier Task peut être en attente sur le réseau, ou sur le système de fichiers, etc le fait de le faire n'implique pas de démarrer une nouvelle Thread ou mettre en file d'attente quelque chose sur le ThreadPool .

  2. Dans un contexte de synchronisation géré par un seul thread, le résultat sera d'exécuter la commande Task de manière synchrone, avec une certaine surcharge. C'est le cas du thread de l'interface utilisateur, je parlerai plus en détail de ce qui se passe ci-dessous.

  3. Il est possible d'écrire un TaskScheduler pour toujours exécuter les tâches de manière synchrone. Sur le même thread, qui effectue l'invocation.

    Note : récemment, j'ai écrit un SyncrhonizationContext qui exécute les tâches sur un seul fil. Vous pouvez le trouver à l'adresse suivante Création d'un planificateur de tâches (System.Threading.Tasks.) . Il en résulterait que TaskScheduler avec un appel à FromCurrentSynchronizationContext .

    La valeur par défaut TaskScheduler mettra en file d'attente les invocations à la fonction ThreadPool . Pourtant, lorsque vous attendez sur l'opération, si elle n'a pas été exécutée sur l'ordinateur de l'entreprise, elle ne l'est pas. ThreadPool il essaiera de le retirer de la ThreadPool et l'exécuter en ligne (sur le même thread qui attend... le thread attend de toute façon, donc il n'est pas occupé).

    Note : Une exception notable est un Task marqué avec LongRunning . LongRunning Task s s'exécuteront sur un thread séparé .


Votre question

Si j'ai une tâche qui s'exécute longtemps et qui est liée au CPU (disons qu'elle fait beaucoup de calculs difficiles), alors l'exécution asynchrone de cette tâche doit bloquer un thread, n'est-ce pas ? Quelque chose doit en effet effectuer les calculs. Si je l'attends, un thread est bloqué.

Si vous effectuez des calculs, ils doivent avoir lieu sur un fil, cette partie est juste.

Pourtant, la beauté de async y await est que le fil d'attente ne doit pas être bloqué (nous y reviendrons plus tard). Pourtant, il est très facile de se tirer une balle dans le pied en programmant l'exécution de la tâche attendue sur le même thread que celui qui attend, ce qui entraîne une exécution synchrone (ce qui est une erreur facile dans le thread de l'interface utilisateur).

L'une des principales caractéristiques de async y await est qu'ils prennent le SynchronizationContext de l'appelant. Pour la plupart des fils, cela se traduit par l'utilisation de l'option par défaut TaskScheduler (qui, comme mentionné plus haut, utilise le ThreasPool ). Cependant, pour le thread de l'interface utilisateur, il s'agit de poster les tâches dans la file d'attente des messages, ce qui signifie qu'elles seront exécutées sur le thread de l'interface utilisateur. L'avantage de cette méthode est que vous n'avez pas besoin d'utiliser la fonction Invoke ou BeginInvoke pour accéder aux composants de l'interface utilisateur.

Avant d'entrer dans le détail de comment await a Task depuis le thread de l'interface utilisateur sans le bloquer, je tiens à souligner qu'il est possible d'implémenter une fonction TaskScheduler où si vous await sur un Task vous ne bloquez pas votre fil de discussion ou ne le mettez pas en veilleuse, mais vous laissez votre fil en choisir un autre. Task qui est en attente d'exécution. Quand j'étais backporting de Tasks for .NET 2.0 Je l'ai expérimenté.

Quel est l'exemple d'une méthode véritablement asynchrone et comment fonctionne-t-elle réellement ? Sont-elles limitées aux opérations d'E/S qui tirent parti de certaines capacités matérielles, de sorte qu'aucun thread n'est jamais bloqué ?

Vous semblez confondre asynchrone con ne pas bloquer un fil . Si vous voulez un exemple d'opérations asynchrones dans .NET qui ne nécessitent pas le blocage d'un thread, une façon de procéder que vous trouverez peut-être facile à comprendre est d'utiliser continuations au lieu de await . Et pour les continuations que vous devez exécuter sur le thread de l'interface utilisateur, vous pouvez utiliser TaskScheduler.FromCurrentSynchronizationContext .

Ne mettez pas en place une attente fantaisiste . Et par là, je veux dire utiliser un Timer , Application.Idle ou quelque chose comme ça.

Lorsque vous utilisez async vous dites au compilateur de réécrire le code de la méthode d'une manière qui permet de la casser. Le résultat est similaire aux continuations, avec une syntaxe beaucoup plus pratique. Lorsque le thread atteint un await le site Task sera programmée, et le fil de discussion est libre de continuer après l'exécution de l'opération en cours. async invocation (hors de la méthode). Lorsque le Task est fait, la suite (après le await ) est programmé.

Pour le thread UI, cela signifie qu'une fois qu'il atteint await il est libre de continuer à traiter les messages. Une fois que le message attendu Task est fait, la suite (après le await ) sera programmé. Par conséquent, atteindre await n'implique pas de bloquer le fil.

Pourtant, en ajoutant aveuglément async y await ne résoudra pas tous vos problèmes.

Je vous soumets une expérience. Prenez une nouvelle application Windows Forms, déposez-y une Button et un TextBox et ajoutez le code suivant :

    private async void button1_Click(object sender, EventArgs e)
    {
        await WorkAsync(5000);
        textBox1.Text = @"DONE";
    }

    private async Task WorkAsync(int milliseconds)
    {
        Thread.Sleep(milliseconds);
    }

Cela bloque l'interface utilisateur. Ce qui se passe, c'est que, comme mentionné plus tôt, await utilise automatiquement le SynchronizationContext du fil de l'appelant. Dans ce cas, il s'agit du thread de l'interface utilisateur. Par conséquent, WorkAsync s'exécutera sur le fil d'exécution de l'interface utilisateur.

C'est ce qui se passe :

  • Les threads de l'interface utilisateur reçoivent le message de clic et appellent le gestionnaire d'événement de clic.
  • Dans le gestionnaire d'événement de clic, le thread UI atteint await WorkAsync(5000)
  • WorkAsync(5000) (et la planification de sa suite) est programmée pour s'exécuter sur le contexte de synchronisation actuel, qui est le contexte de synchronisation du thread de l'interface utilisateur ce qui signifie qu'elle affiche un message pour l'exécuter
  • Le thread UI est maintenant libre de traiter d'autres messages.
  • Le thread de l'interface utilisateur choisit le message à exécuter. WorkAsync(5000) et de programmer sa poursuite
  • Le thread de l'interface utilisateur appelle WorkAsync(5000) avec continuation
  • Sur WorkAsync l'interface utilisateur fonctionne Thread.Sleep . L'interface utilisateur est maintenant irresponsable pendant 5 secondes.
  • La suite planifie l'exécution du reste du gestionnaire de l'événement de clic, ce qui est fait en postant un autre message pour le thread de l'interface utilisateur.
  • Le thread UI est maintenant libre de traiter d'autres messages.
  • Le thread de l'interface utilisateur choisit le message à poursuivre dans le gestionnaire de l'événement de clic.
  • Le thread UI met à jour la zone de texte

Le résultat est une exécution synchrone, avec une surcharge.

Oui, vous devez utiliser Task.Delay au lieu de cela. Ce n'est pas la question ; considérez Sleep un substitut de calcul. Le fait est que le simple fait d'utiliser async y await partout ne vous donnera pas une application qui est automatiquement parallèle. Il est bien mieux de choisir ce que vous voulez exécuter sur un thread d'arrière-plan (par exemple, sur l'application ThreadPool ) et ce que vous voulez exécuter sur le thread de l'interface utilisateur.

Maintenant, essayez le code suivant :

    private async void button1_Click(object sender, EventArgs e)
    {
        await Task.Run(() => Work(5000));
        textBox1.Text = @"DONE";
    }

    private void Work(int milliseconds)
    {
        Thread.Sleep(milliseconds);
    }

Vous constaterez que l'attente ne bloque pas l'interface utilisateur. C'est parce que dans ce cas Thread.Sleep est maintenant en cours d'exécution sur le ThreadPool grâce à Task.Run . Et grâce à button1_Click être async une fois que le code atteint await le thread UI est libre de continuer à travailler. Après le Task est fait, le code reprendra après le await grâce au compilateur qui a réécrit la méthode pour permettre précisément cela.

C'est ce qui se passe :

  • Les threads de l'interface utilisateur reçoivent le message de clic et appellent le gestionnaire d'événement de clic.
  • Dans le gestionnaire d'événement de clic, le thread UI atteint await Task.Run(() => Work(5000))
  • Task.Run(() => Work(5000)) (et la planification de sa suite) est programmée pour s'exécuter sur le contexte de synchronisation actuel, qui est le contexte de synchronisation du thread de l'interface utilisateur ce qui signifie qu'elle affiche un message pour l'exécuter
  • Le thread UI est maintenant libre de traiter d'autres messages.
  • Le thread de l'interface utilisateur choisit le message à exécuter. Task.Run(() => Work(5000)) et programmer sa continuation quand elle sera terminée
  • Le thread de l'interface utilisateur appelle Task.Run(() => Work(5000)) avec la suite, cela fonctionnera sur le ThreadPool
  • Le thread UI est maintenant libre de traiter d'autres messages.

Lorsque le ThreadPool Une fois que l'événement est terminé, la continuation planifie l'exécution du reste du gestionnaire de l'événement de clic, en envoyant un autre message au thread de l'interface utilisateur. Lorsque le thread de l'interface utilisateur sélectionne le message à poursuivre dans le gestionnaire d'événement de clic, il met à jour la zone de texte.

6voto

Dan Points 131

Ce sujet est assez vaste et plusieurs discussions peuvent surgir. Cependant, l'utilisation async y await en C# est considéré comme de la programmation asynchrone. Cependant, le fonctionnement de l'asynchronisme est une discussion totalement différente. Jusqu'à la version 4.5 de .NET, il n'y avait pas de mots-clés async et await, et les développeurs devaient développer directement à partir de l'environnement Task Parallel Librar y (TPL). Là, le développeur avait un contrôle total sur quand et comment créer de nouvelles tâches et même des threads. Cependant, cela avait un inconvénient puisque, n'étant pas vraiment un expert en la matière, les applications pouvaient souffrir de gros problèmes de performance et de bogues dus à des conditions de course entre les threads, etc.

À partir de .NET 4.5, les mots-clés async et await ont été introduits, avec une nouvelle approche de la programmation asynchrone. Les mots-clés async et await n'entraînent pas la création de threads supplémentaires. Les méthodes asynchrones ne requièrent pas de multithreading car elles ne s'exécutent pas sur leur propre thread. La méthode s'exécute sur le contexte de synchronisation actuel et utilise le temps du thread uniquement lorsque la méthode est active. Vous pouvez utiliser Task.Run pour déplacer le travail lié au CPU vers un thread d'arrière-plan, mais un thread d'arrière-plan n'est d'aucune utilité pour un processus qui attend simplement que les résultats soient disponibles.

L'approche de la programmation asynchrone basée sur l'asynchronisme est préférable aux approches existantes dans presque tous les cas. En particulier, cette approche est meilleure que BackgroundWorker pour les opérations liées aux entrées-sorties, car le code est plus simple et vous n'avez pas à vous prémunir contre les conditions de course. Vous pouvez lire plus sur ce sujet ICI .

Je ne me considère pas comme une ceinture noire du C# et certains développeurs plus expérimentés pourront soulever d'autres discussions, mais en principe, j'espère avoir réussi à répondre à votre question.

4 votes

Bien qu'informatif, cela ne répond pas à la question de l'OP. La question de l'OP, IMO, est suffisamment spécifique pour qu'une réponse contenant un résumé de l'histoire récente de la programmation asynchrone en C# et un lien renvoyant à l'OP vers le MSDN sur la programmation asynchrone en C# avec Async n'est pas la réponse que l'OP recherche et je pense qu'il est clair que la question de l'OP pourrait rester même après avoir lu MSDN sur la programmation asynchrone en C# avec Async.

0 votes

C'est une bonne chose que vous ayez souligné que les mots-clés async et await n'entraînent pas la création de fils supplémentaires, mais cela ne répond toujours pas complètement à la question du PO, je pense.

0 votes

D'une certaine manière, je suppose que vous avez raison. Cependant, l'OMI ne peut pas vraiment répondre à cette question, ou du moins pas de la manière dont elle est formulée actuellement. Ce que signifie "mathématiques lourdes" est quelque chose de subjectif qui relève de l'imagination de chacun. Dans la plupart des cas, async et await feront l'affaire, ils ne bloqueront pas le thread principal et l'application sera toujours réactive jusqu'à ce qu'elle "attende" un résultat. Merci pour vos commentaires. J'espère que je ferai mieux la prochaine fois :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X