web-dev-qa-db-fra.com

Traitement async. Dans le pool de threads asp.net core et kestrel

Je suis nouveau dans ASP.NET Core et dans C # en général, je viens du monde Java et je suis un peu confus quant à la manière dont fonctionnent les mots-clés async/wait: Cet article explique que si une méthode est marquée comme asynchrone, elle signifie que

"Cette méthode contient un flux de contrôle qui implique l'attente d'opérations asynchrones et sera donc réécrite par le compilateur dans un style de passage continu pour garantir que les opérations asynchrones puissent reprendre cette méthode au bon endroit." Le point entier des méthodes async rester sur le fil actuel autant que possible

Je pensais donc que c’était un moyen très intéressant de passer le contrôle entre une méthode asynchrone et son appelant s’exécutant sur le thread same implémenté au niveau du code octet/de la machine virtuelle. Je veux dire que je m'attendais à ce que le code ci-dessous

public static void Main(string[] args)
{
    int delay = 10;
    var task = doSthAsync(delay);
    System.Console.WriteLine("main 1: " + Thread.CurrentThread.ManagedThreadId);
    // some synchronous processing longer than delay:
    Thread.Sleep(2 * delay)
    System.Console.WriteLine("main 2: " + Thread.CurrentThread.ManagedThreadId);
    task.Wait();
}

public static async Task<String> doSthAsync(int delay)
{
    System.Console.WriteLine("async 1: " + Thread.CurrentThread.ManagedThreadId);
    await Task.Delay(delay);
    System.Console.WriteLine("async 2: " + Thread.CurrentThread.ManagedThreadId);
    return "done";
}

aurait écrit

 async. 1: 1 
 principal 1: 1 
 principal 2: 1 
 asynchrone 2: 1 

en passant le contrôle de doSthAsync à Main avec le mot clé await, puis de nouveau en doSthAsync avec task.Wait()et tout cela sur un seul thread).

Cependant, le code ci-dessus affiche en fait

 asynchrone 1: 1 
 principale 1: 1 
 async. 2: 4 
 principale 2: 1 

ce qui signifie que c'est simplement un moyen de déléguer une méthode "asynchrone" à un thread séparé si le thread actuel est occupé, ce qui correspond presque exactement à opposé à ce que l'article mentionné déclare:

Le modificateur «asynchrone» de la méthode ne signifie pas que «cette méthode est automatiquement planifiée pour s'exécuter sur un thread de travail de manière asynchrone».

Ainsi, étant donné que les "suites" de méthodes asynchrones, apparemment are ou du moins peuvent être programmées pour s'exécuter sur un autre thread, j'ai quelques questions qui semblent fondamentales pour l'évolutivité des applications C #:

Chaque méthode asynchrone peut-elle être exécutée sur un thread nouvellement créé ou le moteur d'exécution C # maintient-il un pool de threads spécial à cette fin et dans ce dernier cas, comment puis-je contrôler la taille de ce pool? UPDATE: grâce à @Servy, je sais maintenant que dans le cas d'applications CMD-line, il s'agira d'un thread de pool de threads, mais je ne sais toujours pas comment contrôler le nombre de ces threads).

Et si vous utilisiez ASP.NET Core, comment puis-je contrôler la taille d’un pool de threads utilisé par Kestrel pour effectuer des opérations de structure d’entité async ou d’autres E/S (réseau)? S'agit-il du même pool de threads que celui utilisé pour accepter les requêtes HTTP?
UPDATE: grâce à this et cet article par Stephen Cleary Je comprends maintenant qu'il fonctionnera par "type-de-single-threadly" par défaut, c'est-à-dire: à un moment donné, il y aura exactement un thread dans le SynchronizationContext d'une requête HTTP donnée, mais ce thread peut être basculé sur un autre lorsqu'une opération asynchrone est marquée comme terminée et que la tâche est reprise. J'ai également appris qu'il est possible d'envoyer n'importe quelle méthode asynchrone "d'un SynchronizationContext" donné à un thread de pool de threads en utilisant ConfigureAwait(false). Néanmoins, je ne sais pas comment contrôler le nombre d'unités d'exécution de pool de threads et s'il s'agit du même pool que celui utilisé par Kestrel pour accepter les requêtes HTTP.

Si je comprends bien si toutes les E/S sont correctement effectuées de manière asynchrone, la taille de ces pools (qu’il s’agisse d’un pool de threads ou de deux pools distincts) n’a rien à voir avec le nombre de connexions sortantes ouvertes vers des ressources externes. comme DB, non? (Les threads exécutant un code asynchrone accepteront en permanence les nouvelles requêtes HTTP et, en conséquence de leur traitement, ouvriront des connexions sortantes (vers une base de données ou vers un serveur Web pour récupérer des ressources sur le Web) aussi rapidement qu'ils le peuvent sans blocage)
Cela signifie que si je ne veux pas tuer ma base de données, je dois définir une limite saine de son pool de connexions et m'assurer que l'attente d'une connexion disponible est également effectuée de manière asynchrone, n'est-ce pas?
En supposant que je sois correct, je souhaite toujours pouvoir limiter le nombre de threads de pool de threads au cas où une opération d'E/S de longue durée serait effectuée par erreur de manière synchrone afin d'éviter qu'un grand nombre de threads ne soient générés en tant que résultat du blocage d'un grand nombre de threads sur cette opération synchrone (c'est-à-dire: je pense qu'il est préférable de limiter les performances de mon application plutôt que de lui donner un nombre insensé de threads à cause d'un tel bug)

J'ai aussi une question "juste par curiosité": pourquoi async/wait n'est pas implémenté pour exécuter des tâches asynchrones sur le même thread? Je ne sais pas comment l'implémenter sous Windows, mais sous Unix, cela pourrait être implémenté en utilisant soit des appels système/interrogation du système, soit des signaux. Je pense donc qu'il est possible d'y parvenir également sous Windows. Ce serait un langage vraiment cool. fonctionnalité en effet.
UPDATE: si je comprends bien, il s’agit de _ (presque
comment cela fonctionnera-t-il si mon SynchronizationContext n’est pas null: le code s'exécutera de la sorte "type-de-single-threadly": à tout moment SynchronizationContext donné contiendra exactement un thread. Toutefois, sa mise en oeuvre fera en sorte qu'une méthode synchrone attende qu'une tâche asynchrone se bloque, n'est-ce pas? (le moteur d'exécution planifiera le marquage de la tâche comme terminée pour s'exécuter sur le même thread qui est en attendant que cette tâche soit terminée)

Merci!

8
morgwai

Par défaut, lorsqu'il n'y a pas SynchronizationContext ou lorsque ConfigureAwait(false) est appelé sur une tâche attendue, sa poursuite est exécutée sur le pool de threads géré par le CLR auquel on peut accéder à l'aide de méthodes statiques à partir de ThreadPool class
dans les méthodes de base .net de ThreadPool pour contrôler sa taille ( setMaxThreads et setMinThreads ) ne sont pas encore implémentés:
https://github.com/dotnet/cli/issues/889
https://github.com/dotnet/corefx/issues/5920
il est toutefois possible de définir sa taille de manière statique dans le fichier project.json comme expliqué ici:
https://github.com/dotnet/cli/blob/rel/1.0.0/Documentation/specs/runtime-configuration-file.md#runtimeoptions-section-runtimeconfigjson

kestrel a son propre pool pour le traitement asynchrone des requêtes avec libuv: il est contrôlé par static ThreadCount property of KestrelServerOptions class.

articles Liés:
http://blog.stephencleary.com/2012/02/async-and-await.html
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
https://blogs.msdn.Microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/
https://msdn.Microsoft.com/en-us/magazine/gg598924.aspx

merci à @Servy et @Damien_The_Unbeliever pour les astuces qui m'ont permis de le trouver

5
morgwai

await utilise la valeur de SynchronizationContext.Current pour planifier les prolongations. Dans une application console, il n'y a pas de contexte de synchronisation actuel par défaut; il va donc simplement utiliser des threads de pool de threads pour planifier les continuations. Dans les applications avec une boucle de message, telles que winforms, WPF ou les applications winphone, la boucle de message définira le contexte de synchronisation actuel sur celui qui enverra tous les messages à la boucle de message, les exécutant ainsi sur le thread d'interface utilisateur. 

Dans les applications ASP, il y aura également un contexte de synchronisation actuel, mais ce n'est pas un thread particulier. En revanche, lorsqu'il est temps d'exécuter le message suivant pour le contexte de synchronisation, il prend un thread de pool de threads, le configure avec les données de la requête pour la bonne requête, puis le fait exécuter. Cela signifie que lorsque vous utilisez le contexte de synchronisation dans une application ASP, vous savez qu'il n'y a jamais plus d'une opération à la fois, mais qu'il ne s'agit pas nécessairement d'un seul thread gérant chaque réponse.

9
Servy

Il vaut la peine de mentionner ici que la question concerne dot net Core , que contrairement à .Net il n’est pas a SynchronizationContext dans dot net noyau

votre tâche asynchrone sera exécutée à l'aide de n'importe quel thread pour le pool de threads. 

ConfigureAwait n'est pas pertinent, sauf si vous pensez qu'il sera utilisé dans .Net

voir https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html

et ConfigureAwait (false) est-il pertinent dans ASP.NET Core?

0
AmitE