Nach dieser Frage kann ich problemlos asynchrone Vorgänge in ASP.NET MVC verwenden. Also habe ich zwei Blog-Beiträge dazu geschrieben:
Ich habe zu viele Missverständnisse über asynchrone Vorgänge unter ASP.NET MVC.
Ich höre immer diesen Satz: Die Anwendung kann besser skaliert werden, wenn Vorgänge asynchron ausgeführt werden
Und ich habe diese Art von Sätzen auch oft gehört : Wenn Sie ein großes Verkehrsaufkommen haben, ist es möglicherweise besser, wenn Sie Ihre Abfragen nicht asynchron ausführen. Wenn Sie zwei zusätzliche Threads für die Bearbeitung einer Anfrage benötigen, werden Ressourcen von anderen eingehenden Anfragen abgezogen.
Ich denke, diese beiden Sätze sind inkonsistent.
Ich habe nicht viele Informationen darüber, wie Threadpool unter ASP.NET funktioniert, aber ich weiß, dass Threadpool eine begrenzte Größe für Threads hat. Der zweite Satz muss sich also auf dieses Thema beziehen.
Und ich möchte wissen, ob asynchrone Vorgänge in ASP.NET MVC einen Thread von ThreadPool unter .NET 4 verwenden.
Wie strukturiert die App beispielsweise, wenn wir einen AsyncController implementieren? Ist es eine gute Idee, AsyncController zu implementieren, wenn ich großen Datenverkehr bekomme?
Gibt es jemanden da draußen, der diesen schwarzen Vorhang vor meinen Augen wegnehmen und mir den Deal über Asynchronität unter ASP.NET MVC 3 (NET 4) erklären kann?
Bearbeiten:
Ich habe dieses Dokument fast hunderte Male gelesen und verstehe das Hauptgeschäft, aber ich habe immer noch Verwirrung, weil es zu viele inkonsistente Kommentare gibt.
Verwenden eines asynchronen Controllers in ASP.NET MVC
Bearbeiten:
Nehmen wir an, ich habe eine Controller-Aktion wie unten (keine Implementierung von AsyncController
):
public ViewResult Index() {
Task.Factory.StartNew(() => {
//Do an advanced looging here which takes a while
});
return View();
}
Wie Sie hier sehen, feuere ich eine Operation ab und vergesse sie. Dann kehre ich sofort zurück, ohne darauf zu warten, dass es abgeschlossen ist.
Muss in diesem Fall ein Thread aus dem Threadpool verwendet werden? Wenn ja, was passiert nach Abschluss des Vorgangs mit diesem Thread? Kommt es GC
rein und räumt es gleich nach Abschluss auf?
Bearbeiten:
Für die Antwort von @ Darin finden Sie hier ein Beispiel für asynchronen Code, der mit der Datenbank kommuniziert:
public class FooController : AsyncController {
//EF 4.2 DbContext instance
MyContext _context = new MyContext();
public void IndexAsync() {
AsyncManager.OutstandingOperations.Increment(3);
Task<IEnumerable<Foo>>.Factory.StartNew(() => {
return
_context.Foos;
}).ContinueWith(t => {
AsyncManager.Parameters["foos"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<Bars>>.Factory.StartNew(() => {
return
_context.Bars;
}).ContinueWith(t => {
AsyncManager.Parameters["bars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
Task<IEnumerable<FooBar>>.Factory.StartNew(() => {
return
_context.FooBars;
}).ContinueWith(t => {
AsyncManager.Parameters["foobars"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
}
public ViewResult IndexCompleted(
IEnumerable<Foo> foos,
IEnumerable<Bar> bars,
IEnumerable<FooBar> foobars) {
//Do the regular stuff and return
}
}
quelle
Antworten:
Hier ist ein ausgezeichneter Artikel , den Sie lesen sollten, um die asynchrone Verarbeitung in ASP.NET besser zu verstehen (was asynchrone Controller im Grunde darstellen).
Betrachten wir zunächst eine standardmäßige synchrone Aktion:
Wenn eine Anforderung an diese Aktion gestellt wird, wird ein Thread aus dem Thread-Pool gezogen und der Hauptteil dieser Aktion wird für diesen Thread ausgeführt. Wenn die Verarbeitung in dieser Aktion langsam ist, blockieren Sie diesen Thread für die gesamte Verarbeitung, sodass dieser Thread nicht zur Verarbeitung anderer Anforderungen wiederverwendet werden kann. Am Ende der Anforderungsausführung wird der Thread an den Thread-Pool zurückgegeben.
Nehmen wir nun ein Beispiel für das asynchrone Muster:
Wenn eine Anforderung an die Index-Aktion gesendet wird, wird ein Thread aus dem Thread-Pool gezogen und der Hauptteil der
IndexAsync
Methode ausgeführt. Sobald die Ausführung dieser Methode abgeschlossen ist, wird der Thread an den Thread-Pool zurückgegeben.AsyncManager.OutstandingOperations
Wenn Sie dann unter Verwendung des Standards den Abschluss des asynchronen Vorgangs signalisieren, wird ein weiterer Thread aus dem Thread-Pool gezogen und der Hauptteil derIndexCompleted
Aktion darauf ausgeführt und das Ergebnis an den Client gerendert.In diesem Muster sehen wir also, dass eine einzelne Client-HTTP-Anforderung von zwei verschiedenen Threads ausgeführt werden kann.
Nun geschieht der interessante Teil innerhalb der
IndexAsync
Methode. Wenn Sie eine Blockierungsoperation darin haben, verschwenden Sie den gesamten Zweck der asynchronen Controller vollständig, weil Sie den Arbeitsthread blockieren (denken Sie daran, dass der Hauptteil dieser Aktion für einen aus dem Thread-Pool gezogenen Thread ausgeführt wird).Wann können wir also die Vorteile von asynchronen Controllern nutzen, die Sie vielleicht fragen?
Meiner Meinung nach können wir am meisten davon profitieren, wenn wir E / A-intensive Vorgänge ausführen (z. B. Datenbank- und Netzwerkaufrufe an Remotedienste). Wenn Sie einen CPU-intensiven Betrieb haben, bringen asynchrone Aktionen keinen großen Nutzen.
Warum können wir also von E / A-intensiven Operationen profitieren? Weil wir E / A-Abschlussports verwenden könnten . IOCP sind äußerst leistungsfähig, da Sie während der Ausführung des gesamten Vorgangs keine Threads oder Ressourcen auf dem Server verbrauchen.
Wie arbeiten Sie?
Angenommen, wir möchten den Inhalt einer Remote-Webseite mithilfe der WebClient.DownloadStringAsync- Methode herunterladen . Sie rufen diese Methode auf, die einen IOCP im Betriebssystem registriert und sofort zurückgibt. Während der Verarbeitung der gesamten Anforderung werden auf Ihrem Server keine Threads verbraucht. Alles passiert auf dem Remote-Server. Dies kann viel Zeit in Anspruch nehmen, ist Ihnen aber egal, da Sie Ihre Arbeitsthreads nicht gefährden. Sobald eine Antwort empfangen wurde, wird der IOCP signalisiert, ein Thread wird aus dem Thread-Pool gezogen und der Rückruf wird für diesen Thread ausgeführt. Aber wie Sie sehen, haben wir während des gesamten Prozesses keine Threads monopolisiert.
Gleiches gilt für Methoden wie FileStream.BeginRead, SqlCommand.BeginExecute, ...
Was ist mit der Parallelisierung mehrerer Datenbankaufrufe? Angenommen, Sie hatten eine synchrone Controller-Aktion, bei der Sie 4 blockierende Datenbankaufrufe nacheinander ausgeführt haben. Es ist einfach zu berechnen, dass die Ausführung Ihrer Controller-Aktion ungefähr 800 ms dauert, wenn jeder Datenbankaufruf 200 ms dauert.
Wenn Sie diese Aufrufe nicht nacheinander ausführen müssen, würde eine Parallelisierung die Leistung verbessern?
Das ist die große Frage, die nicht einfach zu beantworten ist. Vielleicht ja vielleicht nein. Es hängt ganz davon ab, wie Sie diese Datenbankaufrufe implementieren. Wenn Sie wie zuvor beschrieben asynchrone Controller und E / A-Abschlussports verwenden, steigern Sie die Leistung dieser Controller-Aktion und anderer Aktionen, da Sie keine Worker-Threads monopolisieren.
Wenn Sie sie jedoch schlecht implementieren (mit einem blockierenden Datenbankaufruf, der für einen Thread aus dem Thread-Pool ausgeführt wird), reduzieren Sie die Gesamtausführungszeit dieser Aktion grundsätzlich auf ungefähr 200 ms, aber Sie hätten also 4 Worker-Threads verbraucht Möglicherweise hat sich die Leistung anderer Anforderungen verschlechtert, die aufgrund fehlender Threads im Pool zur Verarbeitung möglicherweise ausgehungert sind.
Es ist daher sehr schwierig. Wenn Sie sich nicht bereit fühlen, umfangreiche Tests für Ihre Anwendung durchzuführen, implementieren Sie keine asynchronen Controller, da Sie wahrscheinlich mehr Schaden als Nutzen anrichten. Implementieren Sie sie nur, wenn Sie einen Grund dafür haben: Sie haben beispielsweise festgestellt, dass Standardaktionen für synchrone Steuerungen einen Engpass für Ihre Anwendung darstellen (natürlich nach ausführlichen Belastungstests und Messungen).
Betrachten wir nun Ihr Beispiel:
Wenn eine Anforderung für die Indexaktion empfangen wird, wird ein Thread aus dem Thread-Pool gezogen, um seinen Body auszuführen, aber sein Body plant nur eine neue Aufgabe mithilfe von TPL . Die Aktionsausführung endet also und der Thread wird an den Thread-Pool zurückgegeben. Abgesehen davon verwendet TPL Threads aus dem Thread-Pool, um ihre Verarbeitung durchzuführen. Selbst wenn der ursprüngliche Thread in den Thread-Pool zurückgegeben wurde, haben Sie einen anderen Thread aus diesem Pool gezeichnet, um den Hauptteil der Aufgabe auszuführen. Sie haben also 2 Threads aus Ihrem wertvollen Pool gefährdet.
Betrachten wir nun Folgendes:
In diesem Fall erzeugen wir manuell einen Thread. In diesem Fall kann die Ausführung des Hauptteils der Indexaktion etwas länger dauern (da das Laichen eines neuen Threads teurer ist als das Ziehen eines Threads aus einem vorhandenen Pool). Die Ausführung des erweiterten Protokollierungsvorgangs erfolgt jedoch für einen Thread, der nicht Teil des Pools ist. Wir gefährden also keine Threads aus dem Pool, die für die Bearbeitung anderer Anforderungen frei bleiben.
quelle
System.Threading.Task
), die in derIndexAsync
Methode ausgeführt werden. Innerhalb dieser Operationen führen wir Datenbankaufrufe an einen Server durch. Alle sind also E / A-intensive Operationen, oder? Erstellen wir in diesem Fall 4 separate Threads (oder erhalten 4 separate Threads aus dem Thread-Pool)? Angenommen, ich habe eine Multi-Core-Maschine, die auch parallel ausgeführt wird, oder?SqlCommand.ExecuteReader
verschwenden Sie alles, da dies ein blockierender Aufruf ist. Sie blockieren den Thread, auf dem dieser Aufruf ausgeführt wird, und wenn dieser Thread zufällig ein Thread aus dem Pool ist, ist er sehr schlecht. Sie profitieren nur, wenn Sie E / A-Abschlussports verwenden :SqlCommand.BeginExecuteReader
. Wenn Sie IOCP nicht verwenden, egal was Sie tun, verwenden Sie keine asynchronen Controller, da Sie der Gesamtleistung Ihrer Anwendung mehr Schaden zufügen als davon profitieren._context.Foo
Sie tatsächlich nichts aus. Sie erstellen nur einen Ausdrucksbaum. Seien Sie dabei äußerst vorsichtig. Die Abfrageausführung wird nur verzögert, wenn Sie mit der Aufzählung über die Ergebnismenge beginnen. Und wenn dies in der Ansicht geschieht, kann dies katastrophal für die Leistung sein. Um eine EF-Abfrage eifrig auszuführen, hängen Sie.ToList()
am Ende an.Ja - alle Threads stammen aus dem Thread-Pool. Ihre MVC-App ist bereits Multithread-fähig. Wenn eine Anfrage eingeht, wird ein neuer Thread aus dem Pool entnommen und zur Bearbeitung der Anfrage verwendet. Dieser Thread wird (von anderen Anforderungen) "gesperrt", bis die Anforderung vollständig bearbeitet und abgeschlossen ist. Wenn im Pool kein Thread verfügbar ist, muss die Anforderung warten, bis einer verfügbar ist.
Wenn Sie über asynchrone Controller verfügen, erhalten diese immer noch einen Thread aus dem Pool, aber während sie die Anforderung bearbeiten, können sie den Thread aufgeben, während sie darauf warten, dass etwas passiert (und dieser Thread kann an eine andere Anforderung übergeben werden) und wenn die ursprüngliche Anforderung einen Thread benötigt wieder bekommt es einen aus dem pool.
Der Unterschied besteht darin, dass Ihnen bei vielen lang laufenden Anforderungen (bei denen der Thread auf eine Antwort von etwas wartet) möglicherweise die Threads aus dem Pool ausgehen, um selbst grundlegende Anforderungen zu bearbeiten. Wenn Sie über asynchrone Controller verfügen, haben Sie keine weiteren Threads, aber die wartenden Threads werden an den Pool zurückgegeben und können andere Anforderungen bearbeiten.
Ein fast richtiges Beispiel ... Denken Sie daran , wie auf einen Bus bekommen, gibt es fünf Menschen warten auf bekommen, die erste wird auf zahlt und setzt sich (der Fahrer ihre Anforderung bedient), Sie erhalten (der Fahrer bedient Ihre Anfrage), aber Sie können Ihr Geld nicht finden; Wenn Sie in Ihren Taschen herumfummeln, gibt der Fahrer Sie auf und setzt die nächsten zwei Personen ein (um ihre Anfragen zu bearbeiten). Wenn Sie Ihr Geld finden, beginnt der Fahrer erneut, sich um Sie zu kümmern (um Ihre Anfrage zu erfüllen) - die fünfte Person muss warten, bis Sie sind fertig, aber die dritte und vierte Person wurden bedient, während Sie auf halbem Weg waren. Dies bedeutet, dass der Fahrer der einzige Thread aus dem Pool ist und die Passagiere die Anforderungen sind. Es war zu kompliziert zu schreiben, wie es funktionieren würde, wenn es zwei Treiber gäbe, aber Sie können sich vorstellen ...
Ohne einen asynchronen Controller müssten die Passagiere hinter Ihnen ewig warten, während Sie nach Ihrem Geld suchen, während der Busfahrer keine Arbeit erledigen würde.
Die Schlussfolgerung lautet also: Wenn viele Leute nicht wissen, wo sich ihr Geld befindet (dh eine lange Zeit benötigen, um auf etwas zu antworten, das der Fahrer gefragt hat), könnten asynchrone Controller den Durchsatz von Anforderungen unterstützen und den Prozess von einigen beschleunigen. Ohne einen Aysnc-Controller warten alle, bis die Person vor ihnen vollständig behandelt wurde. ABER vergessen Sie nicht, dass Sie in MVC viele Bustreiber auf einem einzigen Bus haben, so dass Async keine automatische Wahl ist.
quelle
Hier spielen zwei Konzepte eine Rolle. Zunächst können wir unseren Code parallel ausführen lassen, um ihn schneller auszuführen, oder Code in einem anderen Thread planen, um zu vermeiden, dass der Benutzer wartet. Das Beispiel, das Sie hatten
gehört zur zweiten Kategorie. Der Benutzer erhält eine schnellere Antwort, aber die Gesamtauslastung des Servers ist höher, da er die gleiche Arbeit erledigen und das Threading übernehmen muss.
Ein weiteres Beispiel hierfür wäre:
Da die Anforderungen parallel ausgeführt werden, muss der Benutzer nicht so lange warten, als ob sie seriell ausgeführt würden. Sie sollten sich jedoch darüber im Klaren sein, dass wir hier mehr Ressourcen verbrauchen, als wenn wir seriell ausgeführt würden, da wir den Code in vielen Threads ausführen, während auch ein Thread wartet.
Dies ist in einem Client-Szenario vollkommen in Ordnung. Und es ist dort durchaus üblich, synchronen Code mit langer Laufzeit in eine neue Aufgabe zu verpacken (auf einem anderen Thread auszuführen), um die Benutzeroberfläche ansprechbar zu halten oder zu parallisieren, um sie schneller zu machen. Ein Thread wird jedoch weiterhin für die gesamte Dauer verwendet. Auf einem Server mit hoher Auslastung kann dies nach hinten losgehen, da Sie tatsächlich mehr Ressourcen verwenden. Darum haben dich die Leute gewarnt
Async-Controller in MVC haben jedoch ein anderes Ziel. Hier geht es darum zu vermeiden, dass Threads herum sitzen und nichts tun (was die Skalierbarkeit beeinträchtigen kann). Es ist wirklich nur wichtig, ob die APIs, die Sie aufrufen, über asynchrone Methoden verfügen. Wie WebClient.DowloadStringAsync ().
Der Punkt ist, dass Sie Ihren Thread zurückgeben können, um neue Anforderungen zu verarbeiten, bis die Webanforderung abgeschlossen ist, wo Sie einen Rückruf aufrufen, der denselben oder einen neuen Thread erhält, und die Anforderung beenden.
Ich hoffe, Sie verstehen den Unterschied zwischen asynchron und parallel. Stellen Sie sich parallelen Code als Code vor, in dem sich Ihr Thread befindet, und warten Sie auf das Ergebnis. Während asynchroner Code Code ist, bei dem Sie benachrichtigt werden, wenn der Code fertig ist und Sie wieder daran arbeiten können, kann der Thread in der Zwischenzeit andere Arbeiten ausführen.
quelle
Anwendungen können besser skaliert werden, wenn Vorgänge asynchron ausgeführt werden, jedoch nur, wenn Ressourcen für die Wartung der zusätzlichen Vorgänge verfügbar sind .
Asynchrone Vorgänge stellen sicher, dass Sie eine Aktion niemals blockieren, da eine vorhandene ausgeführt wird. ASP.NET verfügt über ein asynchrones Modell, mit dem mehrere Anforderungen nebeneinander ausgeführt werden können. Es wäre möglich, die Anforderungen in die Warteschlange zu stellen und sie im FIFO zu verarbeiten. Dies lässt sich jedoch nicht gut skalieren, wenn Hunderte von Anforderungen in die Warteschlange gestellt werden und die Verarbeitung jeder Anforderung 100 ms dauert.
Wenn Sie ein großes Verkehrsaufkommen haben, ist es möglicherweise besser, Ihre Abfragen nicht asynchron auszuführen, da möglicherweise keine zusätzlichen Ressourcen für die Bearbeitung der Anforderungen vorhanden sind . Wenn keine freien Ressourcen vorhanden sind, müssen Ihre Anforderungen in die Warteschlange gestellt werden, exponentiell länger dauern oder sofort fehlschlagen. In diesem Fall bietet Ihnen der asynchrone Overhead (Mutexe und Kontextwechselvorgänge) nichts.
Was ASP.NET betrifft, haben Sie keine Wahl - es wird ein asynchrones Modell verwendet, da dies für das Server-Client-Modell sinnvoll ist. Wenn Sie intern Ihren eigenen Code schreiben, der ein asynchrones Muster verwendet, um eine bessere Skalierung zu versuchen, werden Sie keine Verbesserungen feststellen, es sei denn, Sie versuchen, eine Ressource zu verwalten, die von allen Anforderungen gemeinsam genutzt wird, da diese bereits verpackt sind in einem asynchronen Prozess, der nichts anderes blockiert.
Letztendlich ist alles subjektiv, bis Sie tatsächlich sehen, was einen Engpass in Ihrem System verursacht. Manchmal ist es offensichtlich, wo ein asynchrones Muster hilft (indem verhindert wird, dass Ressourcen in der Warteschlange blockiert werden). Letztendlich kann nur das Messen und Analysieren eines Systems anzeigen, wo Sie Effizienzgewinne erzielen können.
Bearbeiten:
In Ihrem Beispiel stellt der
Task.Factory.StartNew
Aufruf eine Operation im .NET-Thread-Pool in die Warteschlange. Die Art der Thread-Pool-Threads muss wiederverwendet werden (um die Kosten für das Erstellen / Zerstören vieler Threads zu vermeiden). Sobald der Vorgang abgeschlossen ist, wird der Thread wieder in den Pool freigegeben, um von einer anderen Anforderung wiederverwendet zu werden (der Garbage Collector wird erst dann beteiligt, wenn Sie einige Objekte in Ihren Vorgängen erstellt haben. In diesem Fall werden sie wie gewohnt erfasst Umfang).Für ASP.NET gibt es hier keine spezielle Operation. Die ASP.NET-Anforderung wird ohne Berücksichtigung der asynchronen Aufgabe abgeschlossen. Die einzige Sorge könnte sein, dass Ihr Thread-Pool überlastet ist (dh, dass derzeit keine Threads verfügbar sind, um die Anforderung zu bearbeiten, und die Einstellungen des Pools nicht zulassen, dass weitere Threads erstellt werden). In diesem Fall wird die Anforderung blockiert und wartet auf den Start der Aufgabe, bis ein Pool-Thread verfügbar wird.
quelle
Task.Factory.StartNew
call wird eine Operation im .NET-Thread-Pool in die Warteschlange gestellt. . In diesem Zusammenhang, welcher hier richtig ist: 1-) Es wird ein neuer Thread erstellt, und wenn dies erledigt ist, kehrt dieser Thread zum Threadpool zurück und wartet dort, bis er wieder verwendet wird. 2-) Es wird ein Thread aus dem Threadpool abgerufen und dieser Thread kehrt zum Threadpool zurück und wartet dort, bis er wieder verwendet wird.3-) Es ist der effizienteste Ansatz und kann beides tun.Ja, sie verwenden einen Thread aus dem Thread-Pool. Es gibt tatsächlich einen ziemlich ausgezeichneten Leitfaden von MSDN, der alle Ihre Fragen und mehr behandelt. Ich habe es in der Vergangenheit als sehr nützlich empfunden. Hör zu!
http://msdn.microsoft.com/en-us/library/ee728598.aspx
In der Zwischenzeit sollten die Kommentare + Vorschläge, die Sie über asynchronen Code hören, mit einem Körnchen Salz aufgenommen werden. Wenn Sie etwas asynchron machen, wird die Skalierung nicht unbedingt verbessert, und in einigen Fällen kann sich die Skalierung Ihrer Anwendung verschlechtern. Der andere Kommentar, den Sie zu "einem riesigen Verkehrsaufkommen ..." gepostet haben, ist ebenfalls nur in bestimmten Kontexten korrekt. Es hängt wirklich davon ab, was Ihre Vorgänge tun und wie sie mit anderen Teilen des Systems interagieren.
Kurz gesagt, viele Leute haben viele Meinungen über Async, aber sie sind möglicherweise nicht kontextbezogen korrekt. Ich würde sagen, konzentrieren Sie sich auf Ihre genauen Probleme und führen Sie grundlegende Leistungstests durch, um festzustellen, mit welchen asynchronen Controllern usw. tatsächlich umgegangen wird Ihrer Anwendung .
quelle
GC
kommt und bereinigt sie direkt, nachdem ich fertig bin, damit meine App keinen Speicherverlust aufweist, und so weiter. Irgendeine Idee dazu.Als erstes ist es nicht MVC, sondern der IIS, der den Thread-Pool verwaltet. Daher wird jede Anforderung, die an eine MVC- oder ASP.NET-Anwendung geht, von Threads bedient, die im Thread-Pool verwaltet werden. Erst wenn er die App Asynch macht, ruft er diese Aktion in einem anderen Thread auf und gibt den Thread sofort frei, damit andere Anforderungen entgegengenommen werden können.
Ich habe dasselbe mit einem Detailvideo ( http://www.youtube.com/watch?v=wvg13n5V0V0/ "MVC Asynch-Controller und Thread-Hunger") erklärt, das zeigt, wie Thread-Hunger in MVC auftritt und wie er durch die Verwendung von MVC minimiert wird Asynch-Controller. Ich habe auch die Anforderungswarteschlangen mit perfmon gemessen, damit Sie sehen können, wie die Anforderungswarteschlangen für MVC-Asynchrone verringert werden und wie schlecht sie für Synchronisierungsvorgänge sind.
quelle