wake-up-neo.com

Synchronen Code in asynchronen Aufruf umbrechen

Ich habe eine Methode in ASP.NET-Anwendung, die ziemlich viel Zeit in Anspruch nimmt, um abzuschließen. Ein Aufruf dieser Methode kann je nach Cache-Status und den vom Benutzer angegebenen Parametern bis zu dreimal während einer Benutzeranforderung erfolgen. Jeder Anruf dauert ca. 1-2 Sekunden. Die Methode selbst ist ein synchroner Aufruf des Dienstes und es gibt keine Möglichkeit, die Implementierung zu überschreiben.
Der synchrone Aufruf des Dienstes sieht also ungefähr so ​​aus:

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

Und die Verwendung der Methode ist (beachten Sie, dass der Aufruf der Methode mehr als einmal vorkommen kann):

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

Ich habe versucht, die Implementierung von meiner Seite aus zu ändern, um eine gleichzeitige Arbeit mit dieser Methode zu ermöglichen, und hier ist, worauf ich bisher gekommen bin.

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

Verwendung (ein Teil des Codes "Anderes erledigen" wird gleichzeitig mit dem Aufruf des Dienstes ausgeführt):

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

Meine Frage ist folgende. Wende ich den richtigen Ansatz an, um die Ausführung in der ASP.NET-Anwendung zu beschleunigen, oder tue ich unnötige Arbeit, um zu versuchen, synchronen Code asynchron auszuführen? Kann jemand erklären, warum der zweite Ansatz in ASP.NET keine Option ist (wenn dies wirklich nicht der Fall ist)? Muss ich eine solche Methode auch asynchron aufrufen, wenn dies der einzige Aufruf ist, den wir derzeit ausführen können (in diesem Fall müssen keine anderen Aufgaben ausgeführt werden, während auf den Abschluss gewartet wird)?
Die meisten Artikel im Netz zu diesem Thema behandeln die Verwendung von async-await Ansatz mit dem Code, der bereits awaitable Methoden bereitstellt, aber das ist nicht mein Fall. Hier ist der Artikel von Nice, der meinen Fall beschreibt, der nicht die Situation von parallelen Anrufen beschreibt und die Option ablehnt, einen Synchronisierungsanruf zu beenden, aber meiner Meinung nach ist meine Situation genau der Anlass, dies zu tun.
Vielen Dank im Voraus für Hilfe und Tipps.

75
Eadel

Es ist wichtig, zwischen zwei verschiedenen Arten von Parallelität zu unterscheiden. Asynchrone Parallelität liegt vor, wenn Sie mehrere asynchrone Vorgänge im Flug haben (und da jeder Vorgang asynchron ist, verwendet keiner von ihnen tatsächlich ein thread ). Parallele Parallelität liegt vor, wenn mehrere Threads jeweils eine separate Operation ausführen.

Das erste, was zu tun ist, ist diese Annahme neu zu bewerten:

Die Methode selbst ist ein synchroner Aufruf des Dienstes und es gibt keine Möglichkeit, die Implementierung zu überschreiben.

Wenn es sich bei Ihrem "Dienst" um einen Webdienst oder einen anderen E/A-gebundenen Dienst handelt, ist es die beste Lösung, eine asynchrone API dafür zu schreiben .

Ich gehe davon aus, dass Ihr "Dienst" eine CPU-gebundene Operation ist, die auf demselben Computer wie der Webserver ausgeführt werden muss.

Wenn dies der Fall ist, ist die nächste zu bewertende Sache eine andere Annahme:

Ich brauche die Anfrage, um schneller auszuführen.

Bist du dir absolut sicher, dass du das tun musst? Gibt es irgendwelche Front-End-Änderungen, die Sie stattdessen vornehmen können - z. B. die Anforderung starten und dem Benutzer erlauben, während der Verarbeitung andere Arbeiten auszuführen?

Ich gehe davon aus, dass Sie die Ausführung der einzelnen Anforderungen wirklich beschleunigen müssen.

In diesem Fall müssen Sie parallelen Code auf Ihrem Webserver ausführen. Dies wird im Allgemeinen definitiv nicht empfohlen, da der parallele Code Threads verwendet, die ASP.NET möglicherweise zur Verarbeitung anderer Anforderungen benötigt, und durch Entfernen/Hinzufügen von Threads die ASP.NET-Threadpool-Heuristik deaktiviert wird. Diese Entscheidung wirkt sich also auf Ihren gesamten Server aus.

Wenn Sie unter ASP.NET parallelen Code verwenden, treffen Sie die Entscheidung, die Skalierbarkeit Ihrer Webanwendung wirklich einzuschränken. Es kann auch vorkommen, dass Sie eine erhebliche Menge an Thread-Abwanderungen feststellen, insbesondere, wenn Ihre Anforderungen überhaupt nicht mehr stimmen. Ich empfehle, unter ASP.NET nur Parallelcode zu verwenden, wenn Sie wissen , dass die Anzahl der gleichzeitigen Benutzer sehr gering ist (d. H. Kein öffentlicher Server).

Wenn Sie also so weit kommen und sicher sind, dass Sie die Parallelverarbeitung unter ASP.NET ausführen möchten, haben Sie mehrere Möglichkeiten.

Eine der einfacheren Methoden ist die Verwendung von Task.Run, Ihrem vorhandenen Code sehr ähnlich. Ich empfehle jedoch nicht, eine CalculateAsync -Methode zu implementieren, da dies impliziert, dass die Verarbeitung asynchron ist (was nicht der Fall ist). Verwenden Sie stattdessen Task.Run zum Zeitpunkt des Anrufs:

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

Wenn es mit Ihrem Code gut funktioniert, können Sie alternativ den Typ Parallel verwenden, d. H. Parallel.For, Parallel.ForEach, oder Parallel.Invoke. Der Vorteil des Parallel-Codes besteht darin, dass der Anforderungsthread als einer der parallelen Threads verwendet wird und dann im Thread-Kontext ausgeführt wird (weniger Kontextwechsel als im async-Beispiel):

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

Ich empfehle überhaupt nicht, Parallel LINQ (PLINQ) für ASP.NET zu verwenden.

95
Stephen Cleary