Encapsuler le code synchrone dans un appel asynchrone

J'ai une méthode dans ASP.NET application, qui consomme beaucoup de temps à remplir. Un appel à cette méthode peut se produire jusqu'à 3 fois au cours d'une requête Utilisateur, en fonction de l'état du cache et des paramètres fournis par l'utilisateur. Chaque appel prend environ 1-2 secondes pour terminer. La méthode elle-même est un appel synchrone au service et il n'y a aucune possibilité de remplacer l'implémentation.
Ainsi, l'appel synchrone au service ressemble à ce qui suit:

public OutputModel Calculate(InputModel input)
{
    // do some stuff
    return Service.LongRunningCall(input);
}

Et le l'utilisation de la méthode est (notez que cet appel de méthode peut se produire plus d'une fois):

private void MakeRequest()
{
    // a lot of other stuff: preparing requests, sending/processing other requests, etc.
    var myOutput = Calculate(myInput);
    // stuff again
}

J'ai essayé de changer l'implémentation de mon côté pour fournir un travail simultané de cette méthode, et voici ce à quoi je suis arrivé jusqu'à présent.

public async Task<OutputModel> CalculateAsync(InputModel input)
{
    return await Task.Run(() =>
    {
        return Calculate(input);
    });
}

UTILISATION (une partie du code "do other stuff" s'exécute simultanément avec l'appel au service):

private async Task MakeRequest()
{
    // do some stuff
    var task = CalculateAsync(myInput);
    // do other stuff
    var myOutput = await task;
    // some more stuff
}

Ma question est la suivante. Est-ce que j'utilise la bonne approche pour accélérer l'exécution dans ASP.NET application ou est-ce que je fais un travail inutile en essayant de courir code synchrone asynchrone? Quelqu'un peut-il expliquer pourquoi la deuxième approche n'est pas une option dans ASP.NET (si ce n'est vraiment pas le cas)? En outre, si une telle approche est applicable, dois-je appeler une telle méthode de manière asynchrone si c'est le seul appel que nous pourrions effectuer en ce moment (j'ai un tel cas, quand il n'y a pas d'autres choses à faire en attendant l'achèvement)?
La plupart des articles sur le net sur ce sujet couvrent l'utilisation de l'approche async-await avec le code, qui fournit déjà des méthodes awaitable, mais ce n'est pas mon cas. Voici le bel article décrivant mon cas, qui ne décrit pas la situation des appels parallèles, en refusant l'option d'envelopper l'appel de synchronisation, mais à mon avis Ma situation est exactement l'occasion de le faire.
Merci d'avance pour l'AIDE et les conseils.

54
demandé sur thumbmunkeys 2014-01-28 17:32:20

1 réponses

Il est important de faire une distinction entre deux types différents de concurrence. asynchrone la concurrence est lorsque vous avez plusieurs opérations asynchrones en vol (et puisque chaque opération est asynchrone, aucune d'entre elles n'utilise réellement un thread). Parallel la concurrence est lorsque vous avez plusieurs threads effectuant chacun une opération distincte.

La première chose à faire est de réévaluer cette hypothèse:

La méthode elle-même est appel synchrone au service et il n'y a aucune possibilité de remplacer l'implémentation.

Si votre "service" est un service web ou toute autre chose liée aux E/S, La meilleure solution consiste à écrire une API asynchrone pour cela.

Je vais continuer avec l'hypothèse que votre "service" est une opération liée au processeur qui doit s'exécuter sur la même machine que le serveur web.

Si c'est le cas, alors la prochaine chose à évaluer est une autre hypothèse:

J'ai besoin de la requête pour s'exécuter plus rapidement.

Êtes-vous absolument sûr que c'est ce que vous devez faire? Y a-t - il des modifications frontales que vous pouvez apporter à la place-par exemple, démarrer la demande et permettre à l'utilisateur de faire d'autres travaux pendant son traitement?

Je vais continuer avec l'hypothèse que oui, vous avez vraiment besoin de rendre la requête individuelle plus rapide.

Dans ce cas, vous devrez exécuter du code parallèle sur votre serveur web. C'est très certainement pas recommandé en général car le code parallèle utilisera des threads qui ASP.NET peut avoir besoin de gérer d'autres requêtes,et en supprimant / ajoutant des threads, il lancera le ASP.NET heuristiques threadpool off. Donc, cette décision a un impact sur l'ensemble de votre serveur.

Lorsque vous utilisez du code parallèle sur ASP.NET, vous prenez la décision de vraiment limiter l'évolutivité de votre application web. Vous pouvez également voir une bonne quantité de désabonnement de thread, surtout si vos demandes sont éclatée à tous. Je recommande uniquement d'utiliser du code parallèle sur ASP.NET si vous savez que le nombre d'utilisateurs simultanés sera assez faible (c'est-à-dire pas un serveur public).

Donc, si vous allez aussi loin, et que vous êtes sûr de vouloir faire un traitement parallèle sur ASP.NET, alors vous avez quelques options.

L'une des méthodes les plus faciles consiste à utiliser Task.Run, très similaire à votre code existant. Cependant, je ne recommande pas d'implémenter une méthode CalculateAsync car cela implique que le traitement est asynchrone (ce qui ne l'est pas). Au lieu de cela, utilisez Task.Run au point de l'appel:

private async Task MakeRequest()
{
  // do some stuff
  var task = Task.Run(() => Calculate(myInput));
  // do other stuff
  var myOutput = await task;
  // some more stuff
}

Sinon, si cela fonctionne bien avec votre code, vous pouvez utiliser le Parallel type, c'est à dire, Parallel.For, Parallel.ForEach, ou Parallel.Invoke. L'avantage du code Parallel est que le thread de requête est utilisé comme l'un des threads parallèles, puis reprend l'exécution dans le contexte du thread (il y a moins de commutation de contexte que l'exemple async):

private void MakeRequest()
{
  Parallel.Invoke(() => Calculate(myInput1),
      () => Calculate(myInput2),
      () => Calculate(myInput3));
}

Je ne recommande pas D'utiliser Parallel LINQ (PLINQ) sur ASP.NET at tout.

67
répondu Stephen Cleary 2014-01-28 14:10:24