Verwenden asynchroner Methoden in ASP.NET MVC 4
von Rick Anderson
In diesem Tutorial lernen Sie die Grundlagen des Erstellens einer asynchronen ASP.NET MVC-Webanwendung mit Visual Studio Express 2012 for Web, einer kostenlosen Version von Microsoft Visual Studio. Sie können auch Visual Studio 2012 verwenden.
Ein vollständiges Beispiel für dieses Tutorial auf github https://github.com/RickAndMSFT/Async-ASP.NET/
Mit der ASP.NET MVC 4 Controller-Klasse in Kombination mit .NET 4.5 können Sie asynchrone Aktionsmethoden schreiben, die ein Objekt vom Typ Task<ActionResult> zurückgeben. Im .NET Framework 4 wurde ein asynchrones Programmierkonzept eingeführt, das als Aufgabe bezeichnet wird, und ASP.NET MVC 4 Task unterstützt. Aufgaben werden durch den Tasktyp und verwandte Typen im System.Threading.Tasks-Namespace dargestellt. Die .NET Framework 4.5 baut auf dieser asynchronen Unterstützung mit den Schlüsselwörtern await und asynchron auf, die das Arbeiten mit Task-Objekten wesentlich weniger komplex machen als frühere asynchrone Ansätze. Die await Schlüsselwort (keyword) ist eine syntaktische Kurzform, die angibt, dass ein Codeteil asynchron auf einen anderen Codeabschnitt warten soll. Die asynchrone Schlüsselwort (keyword) stellt einen Hinweis dar, mit dem Sie Methoden als aufgabenbasierte asynchrone Methoden kennzeichnen können. Die Kombination aus await, async und dem Task-Objekt erleichtert Ihnen das Schreiben von asynchronem Code in .NET 4.5. Das neue Modell für asynchrone Methoden wird als Aufgabenbasiertes asynchrones Muster (TAP) bezeichnet. In diesem Tutorial wird davon ausgegangen, dass Sie mit der asynchronen Programmierung unter Verwendung von Await - und asynchronen Schlüsselwörtern und dem Task-Namespace vertraut sind.
Weitere Informationen zur Verwendung von Await - und asynchronen Schlüsselwörtern und zum Task-Namespace finden Sie in den folgenden Verweisen.
- Whitepaper: Asynchronie in .NET
- Häufig gestellte Fragen zu Async/Await
- Asynchrone Programmierung in Visual Studio
Verarbeiten von Anforderungen durch den Threadpool
Auf dem Webserver verwaltet die .NET Framework einen Pool von Threads, die zum Warten ASP.NET Anforderungen verwendet werden. Wenn eine Anforderung eingeht, wird ein Thread aus dem Pool verteilt, um diese Anforderung zu verarbeiten. Wenn die Anforderung synchron verarbeitet wird, ist der Thread, der die Anforderung verarbeitet, während die Anforderung verarbeitet wird, ausgelastet, und dieser Thread kann keine andere Anforderung verarbeiten.
Dies ist möglicherweise kein Problem, da der Threadpool groß genug sein kann, um viele ausgelastete Threads aufzunehmen. Die Anzahl der Threads im Threadpool ist jedoch begrenzt (das Standardmaximum für .NET 4.5 beträgt 5.000). In großen Anwendungen mit hoher Parallelität von Anforderungen mit langer Ausführungsdauer sind möglicherweise alle verfügbaren Threads ausgelastet. Diese Bedingung wird als Threadmangel (Starvation) bezeichnet. Wenn diese Bedingung erreicht ist, stellt der Webserver Anforderungen in die Warteschlange. Wenn die Anforderungswarteschlange voll ist, lehnt der Webserver Anforderungen mit einem HTTP 503-status (Server zu ausgelastet) ab. Der CLR-Threadpool hat Einschränkungen für neue Threadeinschleusungen. Wenn die Parallelität bursty ist (d. h. ihre Website kann plötzlich eine große Anzahl von Anforderungen erhalten) und alle verfügbaren Anforderungsthreads aufgrund von Back-End-Aufrufen mit hoher Latenz ausgelastet sind, kann die begrenzte Threadeinschleusungsrate dazu führen, dass Ihre Anwendung sehr schlecht reagiert. Darüber hinaus hat jeder neue Thread, der dem Threadpool hinzugefügt wird, Mehraufwand (z. B. 1 MB Stapelspeicher). Eine Webanwendung, die synchrone Methoden verwendet, um Aufrufe mit hoher Latenz zu warten, bei denen der Threadpool auf den .NET 4.5-Standardwert von 5.000 Threads anwächst, würde etwa 5 GB mehr Arbeitsspeicher verbrauchen als eine Anwendung, die dieselben Anforderungen mit asynchronen Methoden und nur 50 Threads für den Dienst verwenden kann. Wenn Sie asynchron arbeiten, verwenden Sie nicht immer einen Thread. Wenn Sie beispielsweise eine asynchrone Webdienstanforderung stellen, verwendet ASP.NET keine Threads zwischen dem Aufruf der asynchronen Methode und dem Await. Die Verwendung des Threadpools für Dienstanforderungen mit hoher Latenz kann zu einem großen Arbeitsspeicherbedarf und einer schlechten Auslastung der Serverhardware führen.
Verarbeiten von asynchronen Anforderungen
In einer Web-App, die beim Starten eine große Anzahl gleichzeitiger Anforderungen sieht oder eine Bursty-Auslastung aufweist (bei der die Parallelität plötzlich zunimmt), erhöht das asynchrone Ausführen von Webdienstaufrufen die Reaktionsfähigkeit der App. Die Verarbeitung einer asynchronen Anforderung dauert genau so lange wie die einer synchronen Anforderung. Wenn eine Anforderung einen Webdienstaufruf ausführt, der zwei Sekunden benötigt, dauert die Anforderung zwei Sekunden, unabhängig davon, ob sie synchron oder asynchron ausgeführt wird. Während eines asynchronen Aufrufs wird ein Thread jedoch nicht daran gehindert, auf andere Anforderungen zu reagieren, während er wartet, bis die erste Anforderung abgeschlossen ist. Daher verhindern asynchrone Anforderungen das Anforderungswarteschlangen- und Threadpoolwachstum, wenn es viele gleichzeitige Anforderungen gibt, die lang andauernde Vorgänge aufrufen.
Auswählen von synchronen oder asynchronen Aktionsmethoden
In diesem Abschnitt werden die Richtlinien aufgeführt, auf deren Grundlage bestimmt wird, wann synchrone oder asynchrone Aktionsmethoden zu verwenden sind. Dies sind nur Richtlinien; Untersuchen Sie jede Anwendung einzeln, um festzustellen, ob asynchrone Methoden bei der Leistung helfen.
Verwenden Sie im Allgemeinen synchrone Methoden für die folgenden Bedingungen:
- Die Vorgänge sind einfach oder haben eine kurze Ausführungszeit.
- Einfachheit ist wichtiger als die Effizienz.
- Die Vorgänge beanspruchen hauptsächlich die CPU und bringen keine umfangreiche Datenträger- oder Netzwerkauslastung mit sich. Das Verwenden asynchroner Aktionsmethoden für CPU-gebundene Vorgänge bietet keine Vorteile und führt zu einem Mehraufwand.
Verwenden Sie im Allgemeinen asynchrone Methoden für die folgenden Bedingungen:
- Sie rufen Dienste auf, die über asynchrone Methoden genutzt werden können, und verwenden .NET 4.5 oder höher.
- Die Vorgänge sind netzwerkgebunden oder E/A-gebunden und nicht CPU-gebunden.
- Parallelverarbeitung ist wichtiger als die Einfachheit des Codes.
- Sie möchten einen Mechanismus bereitstellen, der Benutzern das Abbrechen einer Anforderung mit langer Laufzeit ermöglicht.
- Wenn der Vorteil des Umschaltens von Threads die Kosten des Kontextwechsels überwiegt. Im Allgemeinen sollten Sie eine Methode asynchron machen, wenn die synchrone Methode auf dem ASP.NET Anforderungsthread wartet, während keine Arbeit ausgeführt wird. Durch asynchrones Ausführen des Aufrufs wird der ASP.NET Anforderungsthreads nicht blockiert, während er auf den Abschluss der Webdienstanforderung wartet.
- Tests zeigen, dass die blockierenden Vorgänge einen Engpass bei der Standortleistung darstellen und dass IIS weitere Anforderungen mithilfe asynchroner Methoden für diese blockierenden Aufrufe verarbeiten kann.
Im herunterladbaren Beispiel wird gezeigt, wie asynchrone Aktionsmethoden effektiv verwendet werden. Das bereitgestellte Beispiel wurde entwickelt, um eine einfache Demonstration der asynchronen Programmierung in ASP.NET MVC 4 mit .NET 4.5 bereitzustellen. Das Beispiel soll keine Referenzarchitektur für die asynchrone Programmierung in ASP.NET MVC sein. Das Beispielprogramm ruft ASP.NET-Web-API Methoden auf, die wiederum Task.Delay aufrufen, um lange ausgeführte Webdienstaufrufe zu simulieren. Die meisten Produktionsanwendungen weisen keine so offensichtlichen Vorteile für die Verwendung asynchroner Aktionsmethoden auf.
Nur wenige Anwendungen erfordern, dass alle Aktionsmethoden asynchron sein müssen. Oft wird durch Konvertieren einiger synchroner Aktionsmethoden in asynchrone Methoden die beste Effizienzsteigerung für den erforderlichen Arbeitsaufwand erzielt.
Die Beispielanwendung
Sie können die Beispielanwendung auf https://github.com/RickAndMSFT/Async-ASP.NET/ der GitHub-Website herunterladen. Das Repository besteht aus drei Projekten:
- Mvc4Async: Das ASP.NET MVC 4-Projekt, das den in diesem Tutorial verwendeten Code enthält. Er führt Web-API-Aufrufe an den WebAPIpgw-Dienst aus.
- WebAPIpgw: Das ASP.NET MVC 4-Web-API-Projekt, das die
Products, Gizmos and Widgets
Controller implementiert. Sie stellt die Daten für das WebAppAsync-Projekt und das Mvc4Async-Projekt bereit. - WebAppAsync: Das ASP.NET Web Forms Projekt, das in einem anderen Tutorial verwendet wird.
Die Synchrone Aktionsmethode von Gizmos
Der folgende Code zeigt die Gizmos
synchrone Aktionsmethode, die zum Anzeigen einer Liste von Gizmos verwendet wird. (Für diesen Artikel ist ein Gizmo ein fiktives mechanisches Gerät.)
public ActionResult Gizmos()
{
ViewBag.SyncOrAsync = "Synchronous";
var gizmoService = new GizmoService();
return View("Gizmos", gizmoService.GetGizmos());
}
Der folgende Code zeigt die GetGizmos
Methode des gizmo-Diensts.
public class GizmoService
{
public async Task<List<Gizmo>> GetGizmosAsync(
// Implementation removed.
public List<Gizmo> GetGizmos()
{
var uri = Util.getServiceUri("Gizmos");
using (WebClient webClient = new WebClient())
{
return JsonConvert.DeserializeObject<List<Gizmo>>(
webClient.DownloadString(uri)
);
}
}
}
Die GizmoService GetGizmos
Methode übergibt einen URI an einen ASP.NET-Web-API HTTP-Dienst, der eine Liste von gizmos-Daten zurückgibt. Das WebAPIpgw-Projekt enthält die Implementierung der Web-API gizmos, widget
und product
der Controller.
Die folgende Abbildung zeigt die Gizmos-Ansicht aus dem Beispielprojekt.
Erstellen einer asynchronen Gizmos-Aktionsmethode
Das Beispiel verwendet die neuen Schlüsselwörter asynchron und await (verfügbar in .NET 4.5 und Visual Studio 2012), damit der Compiler für die Verwaltung der komplizierten Transformationen verantwortlich ist, die für die asynchrone Programmierung erforderlich sind. Mit dem Compiler können Sie Code mithilfe der synchronen Ablaufkonstrukte von C# schreiben, und der Compiler wendet automatisch die Transformationen an, die für die Verwendung von Rückrufen erforderlich sind, um das Blockieren von Threads zu vermeiden.
Der folgende Code zeigt die Gizmos
synchrone Methode und die GizmosAsync
asynchrone Methode. Wenn Ihr Browser das HTML 5-Element <mark>
unterstützt, werden die Änderungen in GizmosAsync
gelb hervorgehoben.
public ActionResult Gizmos()
{
ViewBag.SyncOrAsync = "Synchronous";
var gizmoService = new GizmoService();
return View("Gizmos", gizmoService.GetGizmos());
}
public async Task<ActionResult> GizmosAsync()
{
ViewBag.SyncOrAsync = "Asynchronous";
var gizmoService = new GizmoService();
return View("Gizmos", await gizmoService.GetGizmosAsync());
}
Die folgenden Änderungen wurden angewendet, damit der GizmosAsync
asynchron sein kann.
- Die -Methode ist mit der asynchronen Schlüsselwort (keyword) gekennzeichnet, die den Compiler angibt, Rückrufe für Teile des Textkörpers zu generieren und automatisch ein
Task<ActionResult>
zurückgegebenes zu erstellen. - "Async" wurde an den Methodennamen angefügt. Das Anfügen von "Async" ist nicht erforderlich, ist jedoch die Konvention beim Schreiben asynchroner Methoden.
- Der Rückgabetyp wurde von
ActionResult
inTask<ActionResult>
geändert. Der Rückgabetyp vonTask<ActionResult>
stellt die laufende Arbeit dar und stellt Aufrufern der -Methode ein Handle bereit, über das auf den Abschluss des asynchronen Vorgangs gewartet werden kann. In diesem Fall ist der Aufrufer der Webdienst.Task<ActionResult>
stellt die laufende Arbeit mit einem Ergebnis vonActionResult.
- Die await Schlüsselwort (keyword) wurde auf den Webdienstaufruf angewendet.
- Die asynchrone Webdienst-API wurde (
GetGizmosAsync
) aufgerufen.
Innerhalb des GetGizmosAsync
Methodentexts wird eine andere asynchrone Methode GetGizmosAsync
aufgerufen. GetGizmosAsync
gibt sofort einen Task<List<Gizmo>>
zurück, der schließlich abgeschlossen wird, wenn die Daten verfügbar sind. Da Sie nichts anderes tun möchten, bis Sie über die Gizmo-Daten verfügen, wartet der Code auf die Aufgabe (mithilfe des await-Schlüsselwort (keyword)). Sie können die await-Schlüsselwort (keyword) nur in Methoden verwenden, die mit der asynchronen Schlüsselwort (keyword) versehen sind.
Der await-Schlüsselwort (keyword) blockiert den Thread erst, wenn die Aufgabe abgeschlossen ist. Es registriert den Rest der Methode als Rückruf für die Aufgabe und gibt sofort zurück. Wenn die erwartete Aufgabe schließlich abgeschlossen ist, ruft sie diesen Rückruf auf und setzt die Ausführung der Methode direkt dort fort, wo sie aufgehört hat. Weitere Informationen zur Verwendung der Schlüsselwörter await und async sowie des Task-Namespace finden Sie in den asynchronen Verweisen.
Der folgende Code zeigt die GetGizmos
- und GetGizmosAsync
-Methoden:
public List<Gizmo> GetGizmos()
{
var uri = Util.getServiceUri("Gizmos");
using (WebClient webClient = new WebClient())
{
return JsonConvert.DeserializeObject<List<Gizmo>>(
webClient.DownloadString(uri)
);
}
}
public async Task<List<Gizmo>> GetGizmosAsync()
{
var uri = Util.getServiceUri("Gizmos");
using (HttpClient httpClient = new HttpClient())
{
var response = await httpClient.GetAsync(uri);
return (await response.Content.ReadAsAsync<List<Gizmo>>());
}
}
Die asynchronen Änderungen ähneln denen, die oben an gizmosAsync vorgenommen wurden.
- Die Methodensignatur wurde mit dem asynchronen Schlüsselwort (keyword) versehen, der Rückgabetyp wurde in
Task<List<Gizmo>>
geändert, und Async wurde an den Methodennamen angefügt. - Anstelle der WebClient-Klasse wird die asynchrone HttpClient-Klasse verwendet.
- Die await Schlüsselwort (keyword) wurde auf die asynchronen HttpClient-Methoden angewendet.
Die folgende Abbildung zeigt die asynchrone Gizmo-Ansicht.
Die Browserdarstellung der gizmos-Daten ist identisch mit der Ansicht, die durch den synchronen Aufruf erstellt wurde. Der einzige Unterschied besteht darin, dass die asynchrone Version unter hohen Lasten möglicherweise leistungsfähiger ist.
Paralleles Ausführen mehrerer Vorgänge
Asynchrone Aktionsmethoden haben gegenüber synchronen Methoden einen erheblichen Vorteil, wenn eine Aktion mehrere unabhängige Vorgänge ausführen muss. Im bereitgestellten Beispiel zeigt die synchrone Methode PWG
(für Produkte, Widgets und Gizmos) die Ergebnisse von drei Webdienstaufrufen an, um eine Liste mit Produkten, Widgets und Gizmos abzurufen. Das ASP.NET-Web-API Projekt, das diese Dienste bereitstellt, verwendet Task.Delay, um Latenz oder langsame Netzwerkaufrufe zu simulieren. Wenn die Verzögerung auf 500 Millisekunden festgelegt ist, dauert die asynchrone PWGasync
Methode etwas mehr als 500 Millisekunden, während die synchrone PWG
Version über 1.500 Millisekunden benötigt. Die synchrone PWG
Methode wird im folgenden Code dargestellt.
public ActionResult PWG()
{
ViewBag.SyncType = "Synchronous";
var widgetService = new WidgetService();
var prodService = new ProductService();
var gizmoService = new GizmoService();
var pwgVM = new ProdGizWidgetVM(
widgetService.GetWidgets(),
prodService.GetProducts(),
gizmoService.GetGizmos()
);
return View("PWG", pwgVM);
}
Die asynchrone PWGasync
Methode wird im folgenden Code gezeigt.
public async Task<ActionResult> PWGasync()
{
ViewBag.SyncType = "Asynchronous";
var widgetService = new WidgetService();
var prodService = new ProductService();
var gizmoService = new GizmoService();
var widgetTask = widgetService.GetWidgetsAsync();
var prodTask = prodService.GetProductsAsync();
var gizmoTask = gizmoService.GetGizmosAsync();
await Task.WhenAll(widgetTask, prodTask, gizmoTask);
var pwgVM = new ProdGizWidgetVM(
widgetTask.Result,
prodTask.Result,
gizmoTask.Result
);
return View("PWG", pwgVM);
}
Die folgende Abbildung zeigt die Ansicht, die von der PWGasync-Methode zurückgegeben wurde.
Verwenden eines Abbruchtokens
Asynchrone Aktionsmethoden, die Task<ActionResult>
zurückgegeben werden, können abgebrochen werden, d. h. sie verwenden einen CancellationToken-Parameter , wenn eine mit dem AsyncTimeout-Attribut bereitgestellt wird. Der folgende Code zeigt die GizmosCancelAsync
Methode mit einem Timeout von 150 Millisekunden.
[AsyncTimeout(150)]
[HandleError(ExceptionType = typeof(TimeoutException),
View = "TimeoutError")]
public async Task<ActionResult> GizmosCancelAsync(
CancellationToken cancellationToken )
{
ViewBag.SyncOrAsync = "Asynchronous";
var gizmoService = new GizmoService();
return View("Gizmos",
await gizmoService.GetGizmosAsync(cancellationToken));
}
Der folgende Code zeigt die GetGizmosAsync-Überladung, die einen CancellationToken-Parameter akzeptiert.
public async Task<List<Gizmo>> GetGizmosAsync(string uri,
CancellationToken cancelToken = default(CancellationToken))
{
using (HttpClient httpClient = new HttpClient())
{
var response = await httpClient.GetAsync(uri, cancelToken);
return (await response.Content.ReadAsAsync<List<Gizmo>>());
}
}
In der bereitgestellten Beispielanwendung wird durch Auswählen des Links "Abbruchtokendemo" die GizmosCancelAsync
-Methode aufgerufen und der Abbruch des asynchronen Aufrufs veranschaulicht.
Serverkonfiguration für Webdienstaufrufe mit hoher Parallelität/hoher Latenz
Um die Vorteile einer asynchronen Webanwendung zu nutzen, müssen Sie möglicherweise einige Änderungen an der Standardserverkonfiguration vornehmen. Beachten Sie Folgendes, wenn Sie Ihre asynchrone Webanwendung konfigurieren und testen.
Windows 7, Windows Vista und alle Windows-Clientbetriebssysteme haben maximal 10 gleichzeitige Anforderungen. Sie benötigen ein Windows Server-Betriebssystem, um die Vorteile asynchroner Methoden unter hoher Last zu sehen.
Registrieren Sie .NET 4.5 bei IIS über eine Eingabeaufforderung mit erhöhten Rechten:
%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis -i
Siehe ASP.NET IIS-Registrierungstool (Aspnet_regiis.exe)Möglicherweise müssen Sie das HTTP.sys Warteschlangenlimit von dem Standardwert 1.000 auf 5.000 erhöhen. Wenn die Einstellung zu niedrig ist, wird möglicherweise HTTP.sys Anforderungen mit einem HTTP 503-status abgelehnt. So ändern Sie das HTTP.sys Warteschlangenlimit:
- Öffnen Sie den IIS-Manager, und navigieren Sie zum Bereich Anwendungspools.
- Klicken Sie mit der rechten Maustaste auf den Zielanwendungspool, und wählen Sie Erweiterte Einstellungen aus.
- Ändern Sie im Dialogfeld Erweiterte Einstellungen die Warteschlangenlänge von 1.000 in 5.000.
Beachten Sie in den obigen Bildern, dass .NET Framework als v4.0 aufgeführt wird, obwohl der Anwendungspool .NET 4.5 verwendet. Um diese Diskrepanz zu verstehen, lesen Sie folgendes:
Wenn Ihre Anwendung Webdienste oder System.NET verwendet, um mit einem Back-End über HTTP zu kommunizieren, müssen Sie möglicherweise das connectionManagement/maxconnection-Element erhöhen. Für ASP.NET Anwendungen wird dies durch die AutoKonfigurationsfunktion auf das 12-fache der Anzahl von CPUs beschränkt. Das bedeutet, dass Sie auf einem Quad-Proc höchstens 12 * 4 = 48 gleichzeitige Verbindungen zu einem IP-Endpunkt haben können. Da dies an autoConfig gebunden ist, besteht die einfachste Möglichkeit zum Erhöhen
maxconnection
in einer ASP.NET-Anwendung darin , System.Net.ServicePointManager.DefaultConnectionLimit programmgesteuert in der from-MethodeApplication_Start
in der Datei global.asax festzulegen. Ein Beispiel finden Sie im Beispieldownload.In .NET 4.5 sollte der Standardwert 5000 für MaxConcurrentRequestsPerCPU in Ordnung sein.