Asynchrone Programmierung hat in den letzten Jahren vor allem aus zwei Hauptgründen große Aufmerksamkeit erhalten: Erstens trägt sie zu einer besseren Benutzererfahrung bei, da sie den UI-Thread nicht blockiert und verhindert, dass die Benutzeroberfläche vor dem Ende der Verarbeitung angezeigt wird Schnittstelle hängt. Zweitens hilft es dabei, das System erheblich zu skalieren, ohne zusätzliche Hardware hinzuzufügen.
Das Schreiben von ordnungsgemäßem asynchronem Code zur Verwaltung der Threads selbst ist jedoch eine mühsame Aufgabe. Dennoch haben seine enormen Vorteile dazu geführt, dass viele neue und alte Technologien dazu übergegangen sind, asynchrone Programmierung zu verwenden. Microsoft hat seit der Veröffentlichung stark in .NET 4.0 investiert und anschließend in .NET 4.5 die Schlüsselwörter „async“ und „await“ eingeführt, was die asynchrone Programmierung einfacher denn je macht.
Die asynchrone Funktionalität in ASP.NET war jedoch von Anfang an verfügbar, sie erhielt jedoch nie die Aufmerksamkeit, die sie verdiente. Und angesichts der Art und Weise, wie ASP.NET und IIS Anforderungen verarbeiten, können die Vorteile der asynchronen Implementierung sogar noch größer sein. Durch Asynchronität können wir die Skalierbarkeit von ASP.NET-Anwendungen problemlos erheblich verbessern. Wenn neue Programmierkonstrukte eingeführt werden, wie zum Beispiel die Schlüsselwörter async und waiting, sollten wir auch lernen, die Leistungsfähigkeit der asynchronen Programmierung zu nutzen.
In diesem Blogbeitrag besprechen wir, wie IIS und ASP.NET Anforderungen verarbeiten, schauen uns dann an, wo Asynchronität in ASP.NET verwendet werden kann, und diskutieren schließlich einige, die die Vorteile der asynchronen Szene am besten widerspiegeln.
Wie werden Anfragen behandelt?
Jede ASP.NET-Anfrage durchläuft IIS, bevor sie letztendlich von einem ASP.NET-Handler verarbeitet wird. Zuerst empfängt IIS die Anfrage, und nach der vorläufigen Verarbeitung wird sie an ASP.NET gesendet (es muss eine ASP.NET-Anfrage sein), und dann verarbeitet ASP.NET sie tatsächlich und generiert eine Antwort, und dann wird die Antwort zurückgesendet an den Client über IIS. Auf IIS gibt es einige Arbeitsprozesse, die dafür verantwortlich sind, die Anforderung aus der Warteschlange zu entfernen und das IIS-Modul auszuführen, bevor die Anforderung an die ASP.NET-Warteschlange gesendet wird. ASP.NET selbst erstellt jedoch keine Threads und verfügt auch nicht über einen Thread-Pool zum Verarbeiten von Anforderungen. Stattdessen verwendet es den CLR-Thread-Pool, um daraus Threads zum Verarbeiten von Anforderungen abzurufen. Daher ruft das IIS-Modul ThreadPool.QueueUserWorkItem auf, um die Anforderung zur Verarbeitung durch den CLR-Arbeitsthread in die Warteschlange zu stellen. Wir alle wissen, dass der CLR-Thread-Pool von der CLR verwaltet wird und automatisch angepasst werden kann (das heißt, er erstellt und zerstört Prozesse nach Bedarf). Denken Sie auch hier daran, dass das Erstellen und Zerstören von Threads eine schwere Aufgabe ist, weshalb der CLR-Thread-Pool die Verwendung desselben Threads für mehrere Aufgaben ermöglicht. Schauen wir uns ein Diagramm an, das den Prozess der Anforderungsverarbeitung beschreibt.
Wie Sie im obigen Bild sehen können, wird die Anfrage zuerst von HTTP.sys empfangen und der entsprechenden Anwendungspoolwarteschlange auf Kernelebene hinzugefügt. Anschließend nimmt ein IIS-Arbeitsthread die Anforderung aus der Warteschlange, verarbeitet sie und übergibt sie an die ASP.NET-Warteschlange. Beachten Sie, dass die Anfrage automatisch von IIS zurückgegeben wird, wenn es sich nicht um eine ASP.NET-Anfrage handelt. Schließlich wird ein Thread aus dem CLR-Thread-Pool zugewiesen, um die Anforderung zu verarbeiten.
Was sind die Nutzungsszenarien von Asynchronität in ASP.NET?
Alle Anfragen können grob in zwei Kategorien unterteilt werden:
CPU-gebundene Klasse
E/A-gebundene Klasse
CPU-gebundene Klassenanfragen erfordern CPU-Zeit, Und es wird im selben Prozess ausgeführt; die I/O-gebundene Anforderung selbst blockiert und muss sich auf andere Module verlassen, um I/O-Vorgänge auszuführen und Antworten zurückzugeben. Blockierende Anforderungen stellen ein großes Hindernis für die Verbesserung der Anwendungsskalierbarkeit dar und in den meisten Webanwendungen wird viel Zeit mit dem Warten auf E/A-Vorgänge verschwendet. Daher sind die folgenden Szenarien für die asynchrone Verwendung geeignet:
E/A-gebundene Klassenanforderungen, einschließlich:
Datenbankzugriff
Dateien lesen/schreiben
Webdienstaufrufe
Zugriff auf Netzwerkressourcen
Ereignisgesteuerte Anforderungen wie SignalR
Szenarien, in denen Daten aus mehreren Datenquellen abgerufen werden müssen
Wie Ein Beispiel: Hier ist eine einfache synchrone Seite, die dann in eine asynchrone Seite konvertiert wird. In diesem Beispiel wird eine Verzögerung von 1000 ms festgelegt (um einige umfangreiche Datenbank- oder Webdienstaufrufe usw. zu simulieren) und außerdem wird eine Seite mit WebClient wie folgt heruntergeladen:
protected void Page_Load(object sender, EventArgs e) { System.Threading.Thread.Sleep(1000); WebClient client = new WebClient(); string downloadedContent = client.DownloadString("https://msdn.microsoft.com/en-us/library/hh873175%28v=vs.110%29.aspx"); dvcontainer.InnerHtml = downloadedContent; }
Konvertieren Sie nun die Seite in eine asynchrone Seite, hauptsächlich hier umfasst drei Schritte:
Fügen Sie Async = true in der Seitenanweisung hinzu, um die Seite in eine asynchrone Seite umzuwandeln, wie unten gezeigt:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Home.aspx.cs" Inherits="AsyncTest.Home" Async="true" AsyncTimeout="3000" %>
AsyncTimeout wird hier ebenfalls hinzugefügt (optional), bitte Wählen Sie nach Ihren Bedürfnissen.
2.将此方法转换成异步方法。在这里把Thread.Sleep 与 client.DownloadString 转换成异步方法如下所示:
private async Task AsyncWork() { await Task.Delay(1000); WebClient client = new WebClient(); string downloadedContent = await client.DownloadStringTaskAsync("https://msdn.microsoft.com/en-us/library/hh873175%28v=vs.110%29.aspx "); dvcontainer.InnerHtml = downloadedContent; }
3.现在可以直接在 Page_Load (页面加载)上调用此方法,使其异步,如下所示:
protected async void Page_Load(object sender, EventArgs e) { await AsyncWork(); }
但是这里的 Page_Load 返回的类型是async void,这种情况无论如何都应该避免。我们知道,Page_Load 是整个页面生命周期的一部分,如果我们把它设置成异步,可能会出现一些异常情况和事件,比如生命周期已经执行完毕而页面加载仍在运行。 因此,强烈建议大家使用 RegisterAsyncTask 方法注册异步任务,这些异步任务会在生命周期的恰当时间执行,可以避免出现任何问题。
protected void Page_Load(object sender, EventArgs e) { RegisterAsyncTask(new PageAsyncTask(AsyncWork)); }
现在,页面已经转换成了异步页,它就不再是一个阻塞性请求。
笔者在 IIS8.5 上部署了同步页面和异步页面,并使用突发负载对两者进行了测试。测试结果发现,相同的机器配置,同步页面在2-3秒内只能提取1000个请求,而异步页面能够为2200多个请求提供服务。此后,开始收到超时(Timeout)或服务器不可用(Server Not Available)的错误。虽然两者的平均请求处理时间没有多大差别,但是通过异步页面,可以处理两倍以上的请求。这足以证明异步编程功能强大,所以应该充分利用它的优势。
ASP.NET中还有几个地方也可以引入异步:
编写异步模块
使用IHttpAsyncHandler 或 HttpTaskAsyncHandler 编写异步HTTP处理程序
使用web sockets 或 SignalR
结论
本篇博文中,我们讨论了异步编程,而且发现,新推出的async 和 await关键字,使异步编程变得十分简单。我们讨论的话题包括 IIS和ASP.NET如何处理请求,以及在哪些场景中异步的作用最明显。另外,我们还创建了一个简单示例,讨论了异步页面的优势。最后我们还补充了几个ASP.NET中可以使用异步的地方。