.Net Framework 4.5與Visual Studio 11–使用AsynController設計非同步呼叫

by Vivid 28. 三月 2012 01:05

.NET Magazine國際中文電子雜誌
者:許薰尹
稿:張智凱
文章編號:N120312202
出刊日期:2012/3/29

當一個請求送達伺服端上的ASP.NET應用程式,Web伺服器會從執行緒集區(Thread Pool)取出一條執行緒來處理請求,若這個請求執行時間過久,此執行緒就會被停滯無法進行別的作業。若所有執行緒集區中的執行緒都被停滯了,Web伺服器就沒有辦法接收其它的請求,這種情況稱之為「執行緒耗盡」(Thread Starvation)。若要改善這個問題,可以改用非同步的方式來處理Http請求。

ASP.NET MVC 2版本中就已經提供AsynController類別的支援,但是撰寫起來相當的麻煩,.NET Framework 4.5為C#與Visual Basic程式語言提供了非同步的新功能,利用async與await關鍵字,讓ASP.NET 4.5 MVC 可以利用非同步控制器(Asynchronous Controller)來執行非同步的動作方法(Action Method),讓程式設計的動作變的更為簡單。

你可以利用AsynController類別來撰寫非同步的動作方法(Action Method),來執行須要長時間運算的請求,才不會因等待請求的執行結果,而讓網站停滯無法進行別的作業。AsynController類別通常用來執行呼叫Web Service的動作。

本文將介紹如何在ASP.NET MVC4 Web Application類型的專案來撰寫非同步呼叫的動作。由於本文撰寫時使用的工具是Visual Studio 11 Developer Preview,因此本文探討的內容在正式版上市時可能不適用。

使用AsynController類別

讓我們透過範例來了解非同步的程式碼該如何撰寫。首先建立一個新ASP.NET MVC 4 Web Application。在Visual Studio 11 Developer Preview 選取「File」-「New Project」-「Visual C#」-「Web」-「ASP.NET MVC 4 Web Application」,設定名稱為「MyMVCWeb」,然後按下「OK」按鈕,請參考圖1所示。

clip_image002

圖 1:建立範例專案。

在跳出的「New ASP.NET MVC 4 Project」對話盒中,選取「Internet Application」,View Engine使用預設的「Razor」,然後按下「OK」按鈕,請參考圖2所示。

clip_image004

圖 2:建立Internet Application。

修改HomeController.cs檔案。在「Solution Explorer」視窗,雙擊「MyMVCWeb」-「Controllers」-「HomeController」項目,開啟HomeController.cs設計畫面,在HomeController設計畫面最上方匯入System.Threading.Tasks命名空間:

using System.Threading.Tasks;

若要使用非同步動作方法(Action Method),則Controller不能夠使用繼承預設的System.Web.Mvc.Controller類別,需要修改HomeController類別的定義,使其繼承自System.Web.Mvc命名空間下的AsyncController類別,程式碼看起來如下:

public class HomeController :  AsyncController

特別注意,繼承自AsyncController類別仍舊可以處理同步作業。

修改HomeController類別Index 動作方法(Action Method),在方法前方加上async關鍵字,代表此方法是一個非同步方法,將以非同步方式來執行,另外也讓此方法回傳一個Task<string>型別的物件,它代表一個非同步的工作。

public async Task<string> Index ( )
       {     
           await Task.Delay ( 1000 );        
           return "Hello" ;
       }

Index方法中利用await關鍵字停滯Task.Delay這項作業的執行,暫停1000秒,模擬長時間的工作,Task.Delay方法將會回傳一個Task物件,await關鍵字會要求編譯器以非同步的方式等待Task.Delay方法回傳的Task物件。Index方法最後利用return關鍵字回傳「Hello」字串。

最後來設計顯示執行結果的View,在「Solution Explorer」視窗,雙擊「MyMVCWeb」-「Views」-「Home」-「Index.cshtml」項目,開啟Index.cshtml設計畫面,修改Index.cshtml檔案的內容為:

<h1> @Model </h1>

按F5執行專案,參考以下執行結果,Home控制器的Index動作方法將在網頁中印出「Hello」字串,請參考圖3所示。

clip_image006

圖 3:非同步執行結果。

取消AsynController非同步動作

非同步動作方法(Asynchronous action methods)支援取消的動作,可以讓使用者取消執行中的動作。設計動作如下,延續上個範例,修改HomeController.cs檔案,先在檔案最上方,引用System.Threading命名空間:

using System.Threading;

修改HomeController類別的Index方法,使其傳入一個CancellationToken參數,然後在方法中叫用Task.Delay方法時,將CancellationToken參數代入第二個引數的位置。

 

[ AsyncTimeout ( 500 ) ]
[ HandleError ( ExceptionType = typeof ( TaskCanceledException ) , View = "AsyncTimeout" ) ]
public async Task<string> Index ( CancellationToken cancellationToken )
{
    await Task.Delay ( 1000 , cancellationToken ) ;
    return "Hello" ;
}

Index方法上方設定AsyncTimeout Attribute指明呼叫的逾期時間為500毫秒(毫秒milliseconds,預設的逾期時間為45000毫秒,即45秒),不過Task.Delay方法則將程式暫停執行1000毫秒,如此將會引發執行逾期的例外錯誤。此外Index方法上方也設定了HandleError Attribute,若因逾期觸發TaskCanceledException,則顯示AsyncTimeout View,顯示客製化的錯誤訊息。

請參考圖4所示,接下來在程式設計畫面,Index方法上方,按滑鼠右鍵,從突顯式選單中選取「Add View」項目,新增一個View:

clip_image008

圖 4:新增一個View。

請參考圖5,在「Add View」對話盒中,設定View的名稱為「AsyncTimeout」,當你按下「Add」按鈕,在「Solution Explorer」視窗-「MyMVCWeb」-「Views」-「Home」目錄下會產生一個「AsyncTimeout.cshtml」檔案:

clip_image010

圖 5:新增View以產生一個「AsyncTimeout.cshtml」檔案。

修改「AsyncTimeout.cshtml」檔案,加入以下程式碼與HTML標籤,顯示「等待逾時,取消執行」的客製化訊息:

@{
    ViewBag.Title = "AsyncTimeout" ;
    Layout = "~/Views/Shared/_Layout.cshtml" ;
}

<h2> 等待逾時,取消執行 </h2>

 

最後在「Solution Explorer」視窗開啟MyMVCWeb專案的Web.config檔案 ,在<system.web>標籤下,設定customErrors項目的mode屬性值為「On」,啟用客製化錯誤的功能。

<system.web>
    <customErrors mode = "On" > </customErrors>

按F5執行專案,參考以下執行結果,Home控制器的Index動作方法會因逾期問題而轉向顯示「AsyncTimeout」View的內容,請參考圖6所示。

clip_image012

圖 6:網頁因逾期問題而轉向顯示「AsyncTimeout」View。

使用AsynController呼叫WCF Web API服務

AsynController類別通常用來執行呼叫耗時長久的Web Service之動作。接下來我們將要來探討如何呼叫WCF Web API服務。在.NET Magazine國際中文電子雜誌《.Net Framework 4.5與Visual Studio 11 - WCF Web API (1)》(文章編號:N120112001,出刊日期:2012/01/11) 一文中,介紹如何透過WCF Web API來設計支援REST類型的WCF服務。在這一小節中,我們將介紹如何使用AsynController來呼叫此WCF Web API服務。

在Visual Studio 11 Developer Preview中加入《.Net Framework 4.5與Visual Studio 11 - WCF Web API (1)》一文設計的兩個專案:WCFWebLib.csproj 以及ConsoleHost.csproj。從Visual Studio 11 Developer Preview「File」-「Add」-「Existing Project」選取WCFWebLib.csproj專案,再重複此步驟選取ConsoleHost.csproj專案。

MyMvcWeb專案也需要安裝WebApi擴充程式。在「Solution Explorer」視窗,選取「MyMvcWeb」項目,按滑鼠右鍵,從突顯式選單中,選取「Manage NuGet Packages」-「Install」,安裝WebApi,請參考圖7所示。

clip_image014

圖 7:安裝WebApi。

下一個動作便是在MyMvcWeb專案中,加入服務組件的參考。在「Solution Explorer」視窗,「MyMvcWeb」項目上,按滑鼠右鍵,從突顯式選單中,選取「Add Reference」,開啟「Add Reference」對話盒。選取「Solution」-「Projects」-選取「WCFWebLib」-「Add」。

修改HomeController.cs檔案。在「Solution Explorer」視窗,雙擊「MyMVCWeb」-「Controllers」-「HomeController」項目,開啟HomeController.cs設計畫面,在HomeController設計畫面最上方匯入System.Net.Http以及WCFWebLib命名空間:

using System.Net.Http;

using WCFWebLib;

確認HomeController類別是繼承自AsyncController類別。

public class HomeController : AsyncController

修改HomeController類別Index 動作方法(Action Method),在方法前方加上async關鍵字,讓此方法回傳一個Task<ActionResult>型別。

 

public async Task<ActionResult> Index ( )
      {
          HttpClient client = new HttpClient ( );
          client.BaseAddress = new Uri ( "http://localhost:8000/api/" );
          string uri = " employees/1";
         var resp = await client.GetAsync ( uri );
         Task<Employee> emp = resp.Content.ReadAsAsync<Employee> ( );
         Employee result = emp.Result;
          return View ( result );
      }

在Index動作方法中,利用HttpClient類別的GetAsync方法,以非同步方式取回一號員工的資料,然後將代表員工的Employee物件傳遞到View。

在「Solution Explorer」視窗,雙擊「MyMVCWeb」-「Views」-「Home」-「Index.cshtml」項目,開啟Index.cshtml設計畫面,修改Index.cshtml檔案的內容,利用Model取出一號員工的EmployeeID與EmployeeName的值:

@model WCFWebLib.Employee
員工編號: <h1> @Model.EmployeeID </h1>
員工名稱: <h1> @Model.EmployeeName </h1>

測試範例時,先執行ConsoleHost.exe裝載服務,然後按F5執行MyMVCWeb專案,參考以下執行結果,Home控制器的Index動作方法將在網頁中印出員工編號「1」與員工名稱為「Mary」的字串,請參考圖8所示。

clip_image016

圖 8:叫用WCF Web API服務取回一個員工資料。

呼叫WCF Web API服務取回List<Employee>

在.NET Magazine國際中文電子雜誌《.Net Framework 4.5與Visual Studio 11 - WCF Web API (1)》(文章編號:N120112001,出刊日期:2012/01/11) 一文中所設計的WCF服務還提供一個GetEmployees方法可以回傳List<Employee>型別的物件,取回員工的清單資料。若在現有的ASP.NET MVC 4 Web Application叫用WCF服務的GetEmployees方法,可以修改HomeController類別的Index方法如下:

public async Task<ActionResult> Index ( )
        {
            HttpClient client = new HttpClient ( );
            client.BaseAddress = new Uri ( "http://localhost:8000/api/" );


            string uri = " employees";
            var resp = await client.GetAsync ( uri );

            Task<List<Employee>> emps = resp.Content.ReadAsAsync< List<Employee> > ( );

            List<Employee> result = emps.Result;

            return View ( result );
        }

 

然後修改Index.cshtml檔案,利用一個foreach迴圈,取得List<Employee>物件中所有的Employee資料,利用<ul>與<li>標記,條列出員工資料:

@model List<WCFWebLib.Employee>

<ul class="thumbnails">
    @foreach (var emp in Model)
    {
        <li class="item">
        <span class="image-overlay"> @emp.EmployeeID </span>
           <span class="image-overlay"> @emp.EmployeeName </span>           
        </li>
    }
</ul>

 

測試範例時,先執行ConsoleHost.exe裝載服務,然後按F5執行MyMVCWeb專案,參考以下執行結果,Home控制器的Index動作方法將在網頁中展現員工清單,請參考圖9所示。

clip_image018

圖 9:叫用WCF Web API服務取回員工清單資料。

總結

在可能發生「執行緒耗盡」(Thread Starvation)狀況的ASP.NET應用程式中,您可以考慮使用非同步方式來處理請求,特別是在叫用服務的情況下,來提升網站的執行效能。

透過AsynController類別利用非同步控制器(Asynchronous Controller)來執行非同步的動作方法(Action Method)可以讓IIS快速回應HTTP請求,又可以讓使用者取消執行,以改善執行的效能。

Tags:

.NET Magazine國際中文電子雜誌 | ASP.NET | Visual Studio | WCF Web API | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List