web-dev-qa-db-fra.com

Task.Run est-il considéré comme une mauvaise pratique dans une application Web ASP .NET MVC?

Contexte

Nous développons actuellement une application Web, qui repose sur ASP .NET MVC 5, Angular.JS 1.4, Web API 2 et Entity Framework 6. Pour des raisons d'évolutivité, la lourdeur de l'application Web repose sur le modèle asynchrone/attente. Notre domaine nécessite des calculs gourmands en CPU, ce qui peut prendre quelques secondes (<10s). Dans le passé, certains membres de l'équipe utilisaient Task.Run, afin d'accélérer les calculs. Depuis le démarrage d'un thread supplémentaire à l'intérieur = ASP .NET MVC ou contrôleurs d'API Web est considéré comme une mauvaise pratique (le thread n'est pas connu par IIS, donc pas considéré sur AppDomain Recycle => Voir billet de blog de Stephen Cleary =), ils ont utilisé ConfigureAwait (false).

Exemple

public async Task CalculateAsync(double param1, double param2)
{
    // CalculateSync is synchronous and cpu-intensive (<10s)
    await Task.Run(() => this.CalculateSync(param1, param2))).ConfigureAwait(false);
}

Des questions

  • Y a-t-il un avantage en termes de performances à utiliser Task.Run dans un contrôleur d'API Web asynchrone pour les opérations liées au processeur?
  • ConfigureAwait (false) évite-t-il vraiment la création d'un thread supplémentaire?
44
Fabe

Y a-t-il un avantage en termes de performances à utiliser Task.Run dans un contrôleur d'API Web asynchrone pour les opérations liées au processeur?

Zéro. Aucun. En fait, vous gênez les performances en créant un nouveau thread. Dans le contexte d'une application Web, la génération d'un thread n'est pas la même chose que l'exécution en "arrière-plan". Cela est dû à la nature d'une demande Web. Lorsqu'il y a une demande entrante, un thread est extrait du pool pour traiter la demande. L'utilisation de async permet au thread d'être retourné avant la fin de la demande, si et seulement si le thread est dans un état d'attente, c'est-à-dire inactif. La création d'un thread sur lequel travailler, inactive efficacement le thread principal, ce qui lui permet d'être renvoyé au pool, mais vous avez toujours un thread actif. Le retour du thread d'origine dans le pool ne fait rien à ce stade. Ensuite, lorsque le nouveau thread a terminé son travail, vous devez demander un thread principal au pool et enfin renvoyer la réponse. La réponse ne peut pas être renvoyée tant que tout le travail n'est pas terminé, donc que vous utilisiez 1 thread ou cent, asynchrone ou sync, la réponse ne peut pas être retournée tant que tout n'est pas terminé. Par conséquent, l'utilisation de threads supplémentaires ne fait qu'ajouter des frais généraux.

ConfigureAwait (false) évite-t-il vraiment la création d'un thread supplémentaire?

Non, ou plus adéquatement, ce n'est pas ça. ConfigureAwait n'est qu'un indice d'optimisation et détermine uniquement si le contexte d'origine est conservé entre les sauts de thread. Long et court, cela n'a rien à voir avec la création d'un thread, et au moins dans le contexte d'une application ASP.NET, l'impact sur les performances est négligeable dans les deux cas.

51
Chris Pratt

Y a-t-il un avantage en termes de performances à utiliser Task.Run dans un contrôleur API Web asynchrone pour les opérations liées au processeur?

Non. Et peu importe qu'il soit lié au processeur ou non.

Task.Run les déchargements fonctionnent sur un thread ThreadPool. La demande de l'API Web utilise déjà un thread ThreadPool, vous limitez donc l'évolutivité en déchargeant sur un autre thread sans raison.

Cela est utile dans les applications d'interface utilisateur, où le thread d'interface utilisateur est un thread unique spécial.

ConfigureAwait (false) évite-t-il vraiment la création d'un thread supplémentaire?

Cela n'affecte pas la création de threads d'une manière ou d'une autre. Tout ce qu'il fait est de configurer s'il faut reprendre sur le SynchronizationContext capturé ou non.

21
i3arnon

Y a-t-il un avantage en termes de performances à utiliser Task.Run dans un contrôleur d'API Web asynchrone pour les opérations liées au processeur?

Réfléchissez à ce qui se passe réellement - Task.Run() crée une tâche sur le pool de threads, et votre opérateur await libérera le thread (je suppose que toutes les méthodes de la pile sont également en attente). Maintenant, votre thread est de retour dans la piscine, et il pourrait reprendre cette même tâche! Dans ce scénario, il n'y a évidemment aucun gain de performances. Il y a un impact sur les performances, en fait. Mais si le thread récupère une autre tâche (c'est probablement ce qui se passera), un autre thread devra récupérer la tâche CalculateSync() et continuer là où la première s'est arrêtée. Il aurait été plus logique de laisser le thread d'origine exécuter CalculateSync() en premier lieu, sans tâches impliquées, et de laisser l'autre thread avoir les autres tâches en file d'attente.

ConfigureAwait (false) évite-t-il vraiment la création d'un thread supplémentaire?

Pas du tout. Il indique simplement que la suite ne doit pas être exécutée dans le contexte de l'appelant.

9
shay__

Il y a encore une chose que vous devez considérer. Comme vous l'avez dit, votre API exécute une tâche gourmande en processeur, puis asynchronisez/attendez l'aide pour exécuter le processus dans un autre thread et libérez votre pool d'applications pour une autre demande. Signifie que votre API web peut gérer plus de nombre de requêtes par seconde si vous utilisez correctement async/wait.

Dans votre cas, l'apparence de this.CalculateSync(param1, param2) n'est pas une méthode asynchrone, donc pour appeler cela de manière asynchrone, vous devez utiliser Task.Run.

Je recommanderai de supprimer .ConfigureAwait(false) car cela réduira réellement vos performances.

2
Neha Jain