비동기 프로그래밍은 최근 몇 년 동안 많은 주목을 받았는데, 그 이유는 주로 두 가지입니다. 첫째, UI 스레드를 차단하지 않고 처리가 끝나기 전에 UI가 나타나는 것을 방지하므로 더 나은 사용자 경험을 제공하는 데 도움이 됩니다. 인터페이스가 중단됩니다. 둘째, 추가 하드웨어를 추가하지 않고도 시스템을 크게 확장할 수 있습니다.
그러나 스레드 자체를 관리하기 위해 적절한 비동기 코드를 작성하는 것은 지루한 작업입니다. 그럼에도 불구하고, 그 엄청난 이점으로 인해 많은 신구 기술이 비동기 프로그래밍을 사용하기 시작했습니다. Microsoft는 .NET 4.0이 출시된 이후 이에 많은 투자를 했으며 이후 .NET 4.5에 async 및 Wait 키워드를 도입하여 비동기 프로그래밍을 그 어느 때보다 쉽게 만들었습니다.
그러나 ASP.NET의 비동기 기능은 처음부터 사용할 수 있었지만 마땅한 관심을 받지 못했습니다. 그리고 ASP.NET과 IIS가 요청을 처리하는 방식을 고려할 때 비동기 구현의 이점은 훨씬 더 클 수 있습니다. 비동기식을 통해 ASP.NET 응용 프로그램의 확장성을 쉽게 크게 향상시킬 수 있습니다. async 및 Wait 키워드와 같은 새로운 프로그래밍 구성이 도입됨에 따라 우리는 비동기 프로그래밍의 강력한 기능을 사용하는 방법도 배워야 합니다.
이 블로그 게시물에서는 IIS와 ASP.NET이 요청을 처리하는 방법에 대해 논의한 다음 ASP.NET에서 비동기식을 사용할 수 있는 위치를 살펴보고 마지막으로 비동기식 장면의 장점을 가장 잘 반영하는 몇 가지에 대해 논의합니다.
요청은 어떻게 처리됩니까?
모든 ASP.NET 요청은 최종적으로 ASP.NET 처리기에 의해 처리되기 전에 IIS를 통과합니다. 먼저 IIS가 요청을 받아 사전 처리를 한 후 ASP.NET으로 전송하고(ASP.NET 요청이어야 함) ASP.NET이 실제로 이를 처리하여 응답을 생성한 후 다시 응답을 보냅니다. IIS를 통해 클라이언트에. IIS에는 요청을 ASP.NET 대기열로 보내기 전에 요청을 대기열에서 빼고 IIS 모듈을 실행하는 작업을 담당하는 일부 작업자 프로세스가 있습니다. 그러나 ASP.NET 자체는 스레드를 생성하지 않으며 요청을 처리하기 위한 스레드 풀도 없습니다. 대신 CLR 스레드 풀을 사용하여 요청을 처리하기 위해 스레드를 얻습니다. 따라서 IIS 모듈은 ThreadPool.QueueUserWorkItem을 호출하여 CLR 작업자 스레드에서 처리할 요청을 대기열에 넣습니다. 우리 모두는 CLR 스레드 풀이 CLR에 의해 관리되고 자동으로 조정될 수 있다는 것을 알고 있습니다. 즉, 필요에 따라 프로세스를 생성하고 제거합니다. 또한 스레드를 만들고 삭제하는 것은 무거운 작업이므로 CLR 스레드 풀에서는 동일한 스레드를 여러 작업에 사용할 수 있습니다. 요청 처리 프로세스를 설명하는 다이어그램을 살펴보겠습니다.
위 이미지에서 볼 수 있듯이 요청은 먼저 HTTP.sys에서 수신되어 해당 커널 수준 응용 프로그램 풀 대기열에 추가됩니다. 그런 다음 IIS 작업자 스레드가 큐에서 요청을 가져와 처리한 후 ASP.NET 큐에 전달합니다. 요청이 ASP.NET 요청이 아닌 경우 IIS에서 자동으로 반환됩니다. 마지막으로 요청을 처리하기 위해 CLR 스레드 풀에서 스레드가 할당됩니다.
ASP.NET의 비동기 사용 시나리오는 무엇입니까?
모든 요청은 대략 두 가지 범주로 나눌 수 있습니다.
CPU Bound 클래스
I/O Bound 클래스
CPU Bound 클래스 요청에는 CPU 시간이 필요합니다. 그리고 동일한 프로세스에서 실행됩니다. I/O Bound 유형 요청 자체가 차단되며 I/O 작업을 수행하고 응답을 반환하기 위해 다른 모듈에 의존해야 합니다. 차단 요청은 애플리케이션 확장성을 향상시키는 데 주요 장애물이며, 대부분의 웹 애플리케이션에서는 I/O 작업을 기다리는 데 많은 시간이 낭비됩니다. 따라서 다음 시나리오는 비동기식 사용에 적합합니다.
다음을 포함한 I/O 바운드 클래스 요청:
데이터베이스 액세스
파일 읽기/쓰기
웹 서비스 호출
네트워크 리소스 액세스
SignalR과 같은 이벤트 기반 요청
여러 데이터 소스에서 데이터를 가져와야 하는 시나리오
예를 들어, 다음은 간단한 동기 페이지를 만든 다음 이를 비동기 페이지로 변환하는 것입니다. 이 예에서는 1000ms의 지연을 설정하고(대규모 데이터베이스 또는 웹 서비스 호출 등을 시뮬레이션하기 위해) 다음과 같이 WebClient를 사용하여 페이지를 다운로드합니다.
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; }
이제 페이지를 비동기 페이지로 변환합니다. 여기서는 주로 세 단계가 필요합니다.
페이지 지시문에 Async = true를 추가하여 아래와 같이 페이지를 비동기 페이지로 변환합니다.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Home.aspx.cs" Inherits="AsyncTest.Home" Async="true" AsyncTimeout="3000" %>
AsyncTimeout도 여기에 추가됩니다(선택 사항). 귀하의 필요에 따라 선택하십시오.
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中可以使用异步的地方。