在你的服務器端代碼中使用線程和創建異步處理(4)

- 中國WEB開發者網絡 (http://www.webasp.net)
-- 技術教程 (http://www.webasp.net/article/)
--- 在你的服務器端代碼中使用線程和創建異步處理(4) (http://www.webasp.net/article/12/11676.htm)
-- 作者:未知
-- 發佈日期: 2004-07-06
在兩種情況下有如此的不同, 最讓人困擾的問題是響應fast.aspx的頁面比原來要多4秒的時間,原來用非常短的時間.雖然這個例子是我們人為, 但這個例子顯示了當你有如此相關的響應慢的頁面是最糟糕的情形之一. 如果在你的應用中慢的頁面由於行動遲緩影響了CPU的使用, 除了增加更多的硬件設備, 好像沒有好的辦法. 然而, 應用中的慢頁面之所以慢是因為等待non-CPU-bound的操作完成, 問題不在於CPU動力缺乏, 事實上慢的請求進入到線程池中,所以其它的請求必須排隊直到一個線程釋放.

如果查看系統的擴展性,其受到asp.net 線程池不利的影響, 所以你有很少的選擇. 你可以改變線程池的上限. 正如你看到的, 默認的工作線程和I/O線程其上限都是25. 當然, 提高上限是一種比較愚笨粗糙的做法. 如果你能發現恰好的數量來保持CPU的高利用率並且只有在服務器真的非常忙時請求才被延時或拒絕, 那你真是太幸運了. 保持服務真正忙所需的線程數量並非靜止的, 要依賴於各種因素如需求和處理的複雜度而波動.

另外一個潛在的方案是慎重的確認系統那些需要用時較多並且執行non-CPU-bound請求, 然後分派獨特線程來響應它們, 解除原生的線程池的線程來響應額外的請求. 這就是異步處理者.

異步處理者

儘管大部分的asp.net的頁面和處理是由來自同步線程(來自進程級的線程池)響應, 但創建異步請求的處理者是可能的.異步處理者實現了IHttpAsyncHandler 接口.該接口繼承於IHttpHandler:

public interface IHttpAsyncHandler : IHttpHandler

{

IAsyncResult BeginProcessRequest(HttpContext ctx,

AsyncCallback cb,

object obj);

void EndProcessRequest(IAsyncResult ar);

}

實現該接口必須實現另外來自IHttpHandler的兩個標準的方法. 第一個, BeginProcessRequest, 被應用類調用代替直接調用ProcessRequest. 它會讓處理者發起一個新的線程去處理請求並且會立刻從BeginProcessRequest 方法返回, 通過一個引用來驗證IAsyncResult 實例, 目的是讓運行時間直到操作結束. 另外一個方法是EndProcessRequest, 當請求處理完成被調用, 如果有必要會被用來清除任何原來被分派的資源.

如果你做過.Net FrameWork下異步執行的工作, 你應該曉得最簡單和最被推薦的方式使用異步委託代理執行異步工作. 調用異步代理通過調用BeginInvoke 隱式使用來自進程級的線程池的線程來執行委託功能. 使用異步委託調用來實現異步處理者事實上非常直接, 如圖2 所示.

<!-- File: AsyncDelegate.ashx -->
<%@ WebHandler Language="C#"
Class="EssentialAspDotNet.HttpPipeline.AsyncHandler" %>

using System;
using System.Web;
using System.Threading;
using System.Diagnostics;
using System.Reflection;

namespace EssentialAspDotNet.HttpPipeline
{
public delegate void ProcessRequestDelegate(HttpContext ctx);

public class AsyncHandler : IHttpAsyncHandler
{
public void ProcessRequest(HttpContext ctx)
{
System.Threading.Thread.Sleep(2000);
ctx.Response.Output.Write(
"AsyncDelegate, threaded={0}",
AppDomain.GetCurrentThreadId());
}

public bool IsReusable
{
get { return true;}
}

public IAsyncResult BeginProcessRequest(HttpContext ctx,
AsyncCallback cb, object obj)
{
ProcessRequestDelegate prg =
new ProcessRequestDelegate(ProcessRequest);
// Fire-and-forget
return prg.BeginInvoke(ctx, cb, obj);
}

public void EndProcessRequest(IAsyncResult ar)
{
}
}
}

圖二

我實施IHttpAsyncHandler.BeginProcessRequest 調用BeginInvoke 在我的一個ProcessRequestDelegate實例上,

ProcessRequestDelegate 已經被引用我的ProcessRequest 方法初始化. 這在一個分離的線程上執行ProcessRequest是有效的, 從請求線程中分離出來.

運行同樣的壓力測試slow.aspx頁面, 這次用我的新的asyncDelegate.ashx 處理者代替fast.aspx, 產生結果如下,平均響應時間fast.aspx為4.14秒, asyncDelegate.ashx 為7.86秒, 請求次數為957次.

讓人失望, 對於fast.aspx來說響應時間並沒有真正的提高. 由於異步委託調用完成不讓創建一個異步處理者,這是因為它來自同樣的是進程級的CLR線程池(響應請求). 然而一個主要的請求線程確實需要返回給線程池,另外一個線程被取出去執行異步委託, 這意味著這有個用來響應額外請求空線程收穫, 所以處理者沒有用. 你會得到同樣的問題, 如果你用ThreadPool.QueueUserWorkItem 去執行異步請求處理, 以為它也是同樣的方式取得線程.

用定制線程異步處理者

webasp.net