如果只要想使用非同步作業執行運算,可以使用和 WinForm 相同的 APM 技巧即可。 這裡特別指的是網頁的非同步作業,其考量點會和 WinForm 的非同步有所差異。 在 WinForm 中,非同步作業常用來執行耗時的工作,並且適時的回應使用者工作進度。 但是在 WebFrom 中,不管是否使用非同步作業,都比須等待整個網頁處理完畢後才會傳送回應到客戶端,所以網頁非同步不是用來回應使用者工作進度的。
在 WebForm 中使用非同步執行長時間工作的最主要目的是希望善用 ThreadPool 。 因為當 IIS 收到一個要求時,就會由 ThreadPool 中佔用一個 Thread 來處理要求,一直到執行完畢回應用戶端為止。 若 ThreadPool 中的 Thread 被佔用完了,IIS 就會先將用戶端來的要求加入要求佇列(Request Queue),但是如果佇列又1達到 IIS 的上限,IIS 就會暫時無法提供服務,並回應 HTTP 503 服務無法使用 的錯誤訊息。
使用網頁的非同步作業來執行工作,它會將工作交由 CLR 的 Thread 去執行,然後釋放原先 IIS 的 Thread 回 ThreadPool 。 等執行結束再跟 ASP.NET 的 ThreadPool 請求一個 Thread 來回應要求。這樣子 ASP.NET 的 ThreadPool 就有比較多空閒的 Thread 去服務更多的 request 。
IIS 設定佇列長度參考如下圖示:

非同步的 Handlers
You create an asynchronous handler much like you would a synchronous handler. You use a similar interface called IHttpAsyncHandler and then override the IsReusable property and the BeginProcessRequest method. You also provide a callback method that gets called when the asynchronous operation completes. Finally, you write code inside the EndProcessRequest method to deal with any cleanup when the process completes.
1. 建立非同步 Handlers
- 建立非同步 Handlers,必須實作一個 IHttpAsyncHandler 介面的類別,並完成該介面必要的 BeginProcessRequest 和 IsReusable 等方法。
- 在 BeginProcessRequest 方法中,建立實作 IAsyncResult 介面的類別的實體。並叫用該類別的啟動方法。
2. 將執行作業物件化
- 自訂一個實作 IAsyncResult 介面的類別。
- 在自訂類別中,新增一個函式,在此函式中撰寫想要在非同步作業中執行的程式碼。
- 在自訂類別中,新增一個用來啟動非同步作業的方法,在這個方法中,使用 ThreadPool.QueueUserWorkItem 將工作放至執行緒佇列中。
非同步 Handler 範例
public class PhotoHandlerAsync : IHttpAsyncHandler
{
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback, object extraData)
{
string threadid = Thread.CurrentThread.ManagedThreadId.ToString().PadLeft(2);
string EmpID = (context.Request.QueryString["ID"] != null) ? context.Request.QueryString["ID"] : "9411994";
string jobid = EmpID;
myDebug.WriteLine("[{2}][{0}]BeginProcessRequest... {1}", threadid, Tools.GetNow(), jobid);
PhotoHandlerOperation asyncOP = new PhotoHandlerOperation(context, callback, jobid);
myDebug.WriteLine("[{2}][{0}]BeginProcessRequest queued...{1}", threadid, Tools.GetNow(), jobid);
asyncOP.StartAsync(jobid);
return asyncOP;
}
public void EndProcessRequest(IAsyncResult result)
{
string threadid = Thread.CurrentThread.ManagedThreadId.ToString().PadLeft(2);
string jobid = (result.AsyncState != null) ? result.AsyncState.ToString() : "";
myDebug.WriteLine("[{2}][{0}]EndProcessRequest... {1}", threadid, Tools.GetNow(), jobid);
}
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
throw new NotImplementedException();
}
}
public class PhotoHandlerOperation : IAsyncResult
{
private bool _completed;
private object _state;
private AsyncCallback _callback;
private HttpContext _context;
public PhotoHandlerOperation(HttpContext context, AsyncCallback callback, object state)
{
_callback = callback;
_context = context;
_state = state;
_completed = false;
}
public object AsyncState
{
get { return _state; }
}
public System.Threading.WaitHandle AsyncWaitHandle
{
get { return null; }
}
public bool CompletedSynchronously
{
get { return false; }
}
public bool IsCompleted
{
get { return _completed; }
}
public void StartAsync()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(StartAsyncOperation), null);
}
public void StartAsync(object extraData)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(StartAsyncOperation), extraData);
}
public void StartAsyncOperation(object workItemState)
{
string threadid = Thread.CurrentThread.ManagedThreadId.ToString().PadLeft(2);
string jobid = (workItemState!=null)? workItemState.ToString() : "";
string EmpID = (workItemState != null) ? workItemState.ToString() : "9411994";
HttpRequest request = _context.Request;
HttpResponse response = _context.Response;
myDebug.WriteLine("[{2}][{0}]StartAsyncOperation... {1}", threadid, Tools.GetNow(), jobid);
byte[] photo = GetEmpPhoto(EmpID);
if (photo != null)
{
response.ContentType = "image/jpeg";
response.BinaryWrite(photo);
}
else
{
response.Write("./images/noimage.gif");
}
myDebug.WriteLine("[{2}][{0}]FinishAsyncOperation... {1}", threadid, Tools.GetNow(), jobid);
_completed = true;
_callback(this);
}
private byte[] GetEmpPhoto(string EmpID)
{
Thread.Sleep(500);
...
}
}
//[9411981][11]BeginProcessRequest... 14:15:32 8915
//[9411983][ 5]BeginProcessRequest... 14:15:32 8915
//[9411984][14]BeginProcessRequest... 14:15:32 8915
//[9411984][14]BeginProcessRequest queued...14:15:32 9155
//[9411983][ 5]BeginProcessRequest queued...14:15:32 9135
//[9411981][11]BeginProcessRequest queued...14:15:32 9135
//[9411984][ 6]StartAsyncOperation... 14:15:32 9195
//[9411982][ 5]BeginProcessRequest... 14:15:32 9275
//[9411983][14]StartAsyncOperation... 14:15:32 9195
//[9411982][ 5]BeginProcessRequest queued...14:15:32 9335
//[9411981][11]StartAsyncOperation... 14:15:32 9295
//[9411982][ 5]StartAsyncOperation... 14:15:32 9355
//[9411984][ 6]FinishAsyncOperation... 14:15:33 4455
//[9411984][ 6]EndProcessRequest... 14:15:33 4475
//[9411983][14]FinishAsyncOperation... 14:15:33 4705
//[9411982][ 5]FinishAsyncOperation... 14:15:33 4765
//[9411983][14]EndProcessRequest... 14:15:33 4835
//[9411982][ 5]EndProcessRequest... 14:15:33 5035
//[9411981][11]FinishAsyncOperation... 14:15:33 5455
//[9411981][11]EndProcessRequest... 14:15:33 5665
若使用同步的 Handlers
public void ProcessRequest(HttpContext context)
{
string threadid = Thread.CurrentThread.ManagedThreadId.ToString().PadLeft(2);
string EmpID = (context.Request.QueryString["ID"] != null) ? context.Request.QueryString["ID"] : "9411994";
string jobid = EmpID;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
myDebug.WriteLine("[{2}][{0}]BeginProcessRequest... {1}", threadid, Tools.GetNow(), jobid);
byte[] photo = GetEmpPhoto(EmpID);
if (photo != null)
{
response.ContentType = "image/jpeg";
response.BinaryWrite(photo);
}
else
{
response.Write("./images/noimage.gif");
}
myDebug.WriteLine("[{2}][{0}]EndProcessRequest... {1}", threadid, Tools.GetNow(), jobid);
}
private byte[] GetEmpPhoto(string EmpID)
{
Thread.Sleep(500);
...
}
//[9411983][ 6]BeginProcessRequest... 14:18:52 7479
//[9411981][ 5]BeginProcessRequest... 14:18:52 7479
//[9411982][13]BeginProcessRequest... 14:18:52 7759
//[9411983][ 6]EndProcessRequest... 14:18:53 2819
//[9411982][13]EndProcessRequest... 14:18:53 3089
//[9411981][ 5]EndProcessRequest... 14:18:53 3319
//[9411984][ 6]BeginProcessRequest... 14:18:53 3349
//[9411984][ 6]EndProcessRequest... 14:18:53 8640
非同步的網頁
ASP.NET 使用 ThreadPool 來服務 request,但是 ThreadPoll 資源有限,若剛好有多個 request 都提出耗時的需求,那麼 ThreadPool 就比較容易被消耗完,導至效能瓶頸 (performance bottleneck)。 所以使用非同步執行耗時工作,它會釋放 ASP.NET 的 ThreadPool 資源,交尤其他 Thread 去執行,等執行結束再由 ASP.NET 的 ThreadPool 接手管理。 這樣子 ASP.NET 的 ThreadPool 就有比較多空閒的 Thread 去服務更多的 request 。
1. 啟用非同步網頁
在 @ Page 宣告中,加上 Async="true" 屬性宣告。 這個屬性會讓 Page 實作 IHttpAsynchHnadler 介面。
<%@ Page Language="C#" AutoEventWireup="true" Async="true" %>
2. 建立非同步作業的開始和結束函式
接下來在 code-behind 中,建立非同步作業開始的方法(method),和結束的方法。
- BeginAsyncOperation :啟動非同步作業事件。
- EndAsyncOperation :結束非同步作業事件。
protected IAsyncResult BeginProcessRequest(object sender, EventArgs e, AsyncCallback cb, object extraData)
{
string threadid = Thread.CurrentThread.ManagedThreadId.ToString().PadLeft(2);
string jobid = extraData.ToString();
myDebug.WriteLine("[{2}][{0}]BeginProcessRequest... {1}", threadid, Tools.GetNow(), jobid);
PhotoHandlerOperation imageOperation = new PhotoHandlerOperation(this.Context, cb, extraData);
imageOperation.StartAsync(jobid);
myDebug.WriteLine("[{2}][{0}]BeginProcessRequest queued...{1}", threadid, Tools.GetNow(), jobid);
return imageOperation;
}
protected void EndProcessRequest(IAsyncResult result)
{
string jobid = result.AsyncState.ToString();
string threadid = Thread.CurrentThread.ManagedThreadId.ToString().PadLeft(2);
myDebug.WriteLine("[{2}][{0}]EndProcessRequest... {1}", threadid, Tools.GetNow(), jobid);
}
3. 在 Page_Load 事件中,叫用 AddOnPreRenderCompleteAsync 方法
public void AddOnPreRenderCompleteAsync(BeginEventHandler beginHandler, EndEventHandler endHandler); public void AddOnPreRenderCompleteAsync(BeginEventHandler beginHandler, EndEventHandler endHandler, object state);
Page.AddOnPreRenderCompleteAsync 這個方法是用來註冊非同步作業的開始和結束事件處理常式的委派。 在使用上有幾點須要知道:
- 即使註冊了多個處理常式,同一時間只會執行一個處理常式。不過可以在一個處理常式中,同時執行多個非同步作業。
- 必須在 PreRender 之前,使用 AddOnPreRenderCompleteAsync 註冊非同步處理常式。
- 非同步處理常式會在 PreRender 和 PreRenderComplete 事件之間被呼叫執行。

protected void Page_Load(object sender, EventArgs e)
{
//在網頁中使用非同步執行方法
AddOnPreRenderCompleteAsync(BeginProcessRequest, EndProcessRequest, "job1");
AddOnPreRenderCompleteAsync(BeginProcessRequest, EndProcessRequest, "job2");
AddOnPreRenderCompleteAsync(BeginProcessRequest, EndProcessRequest, "job3");
AddOnPreRenderCompleteAsync(BeginProcessRequest, EndProcessRequest, "job4");
}
[job1][ 5]BeginProcessRequest... 13:57:09 7294
[job1][ 5]BeginProcessRequest queued...13:57:09 7324
[job1][ 6]StartAsyncOperation... 13:57:09 7334
[job1][ 6]FinishAsyncOperation... 13:57:10 3374
[job1][ 6]EndProcessRequest... 13:57:10 3384
[job2][ 6]BeginProcessRequest... 13:57:10 3384
[job2][ 6]BeginProcessRequest queued...13:57:10 3394
[job2][ 5]StartAsyncOperation... 13:57:10 3394
[job2][ 5]FinishAsyncOperation... 13:57:10 8634
[job2][ 5]EndProcessRequest... 13:57:10 8634
[job3][ 5]BeginProcessRequest... 13:57:10 8644
[job3][ 5]BeginProcessRequest queued...13:57:10 8644
[job3][ 6]StartAsyncOperation... 13:57:10 8654
[job3][ 6]FinishAsyncOperation... 13:57:11 3775
[job3][ 6]EndProcessRequest... 13:57:11 3785
[job4][ 6]BeginProcessRequest... 13:57:11 3785
[job4][ 6]BeginProcessRequest queued...13:57:11 3795
[job4][ 5]StartAsyncOperation... 13:57:11 3795
[job4][ 5]FinishAsyncOperation... 13:57:11 8835
[job4][ 5]EndProcessRequest... 13:57:11 8835
}
非同步的網頁 2
使用 Page.ExecuteRegisteredAsyncTasks 是另一種向頁面註冊新的非同步工作的方法。 其主要方法是透過 PageAsyncTask 類別,建立一個平行執行物件。
IAsyncResult BeginAsyncOperation(object sender, EventArgs e, AsyncCallback callback, object state)
{
...
}
void EndAsyncOperation(IAsyncResult ar)
{
...
}
protected void btnAsync_Click(object sender, EventArgs e)
{
string ThreadID = Thread.CurrentThread.ManagedThreadId.ToString();
myDebug.WriteLine("[" + ThreadID + "] btnAsync_Click Before RegisterAsyncTask 1 " + Tools.GetNow());
//建立 PageAsyncTask 執行個體
PageAsyncTask asyncTask = new PageAsyncTask(BeginAsyncOperation, EndAsyncOperation, null, "job1", true /*平行處理*/);
//註冊非同步任務
Page.RegisterAsyncTask(asyncTask);
}

沒有留言:
張貼留言