Web開發中使用多線程可以增強使用者體驗,尤其是多使用者、多任務、海量資料和資源緊張的情況下。所以我們的ASP.Net教程設立多線程編程實戰專題。下面這些代碼範例都是入門級的,希望對對大家學習ASP.Net多線程編程有所協助。
ASP.Net教程系列:多線程編程實戰二之ASP.NET使用多線程範例
使用多線程是為了提高CPU的利用率,即在在相同的時間裡面做更多的事情(但前提是系統資源沒有完全耗盡),ASP.NET中使用多線程可以加快頁面在伺服器端的產生速度。一般頁面產生過程中花費時間最多的是資料庫查詢階段,如果你的頁面有10個查詢,不使用多線程的話,這10個查詢將是串列執行的——即依次執行每一個查詢。如果使用多線程,將可以使這10個查詢幾乎同時執行。這顯然會提高頁面的產生速度。
在網上搜尋了些許文章說在IIS進程中使用多線程是不穩定的,可我經過實踐卻發現ASP.NET使用多線程也沒出啥問題。不過在ASP.NET中使用多線程得注意一些地方,不然確實是不穩定,甚至是行不通的。比如不能在多線程中使用HttpContext下的任何方法和屬性,這就包括Cookie、Session、Response、Request、Application等等,當使用這些方法或者屬性的時候,IIS進程將會直接崩潰。更要注意的是,由於多線程與頁面的載入(Load)是非同步執行的,必須讓這些建立的線程在Load執行完之前同步,不然可能導致資料沒有載入成功。 可能會有人問HttpContext等都被限制了,頁面中還能做什麼呢?我們完全可以把建立的線程與頁面主體隔開,把需要的資料先在頁面主體中擷取,然後直接傳入到建立的線程中就可解決。話不多說,具體如何,請看下文。
假設某個頁面中有2個SQL查詢,一個是根據Url傳遞來的參數P確定當前頁的內容,另一個查詢是顯示所有分類。第一個查詢語句需要用到Request.QueryString擷取P傳遞來的頁碼,第二個查詢語句則可直接寫SQL語句。
假設第一個查詢語句如:SELECT * FROM Archives WHERE Page=傳遞的頁碼
假設第二個查詢語句如:SELECT * FROM Category
我們先建立一個類,用於接受參數P和資料繫結控制項ID(此處使用Repeater控制項綁定資料),此類能夠把SQL語句查詢的結果綁定到資料控制項(Repeater)中。
public classBindData
{
private int currentPage = 1;
private Repeater rpID;
public BindData(Repeater rpID)
{
this.rpID = rpID;
}
public BindData(Repeater rpID,int page)
{
this.rpID = rpID;
this.currentPage = page;
}
public void BindCategory()
{
string strSql="SELECT * FROM Category";
this.BindDataToRepeater(strSql, this.rpID);
}
public void BindArchive()
{
string strSql = string.Format("SELECT * FROM Archives WHERE Page={0}",this.currentPage);
this.BindDataToRepeater(strSql, this.rpID);
}
private void BindDataToRepeater(string strSql, Repeater rp)
{
if (rp == null) return;
SqlConnection conn = new SqlConnection("data source=資料服務器地址;User ID=使用者名稱;pwd=密碼;Initial Catalog=資料庫名");
SqlCommand cmd = new SqlCommand(strSql, conn);
SqlDataReader dtr;
try
{
conn.Open();
dtr = cmd.ExecuteReader();
controlID.DataSource = rp;
controlID.DataBind();
if (!dtr.IsClosed)
dtr.Close();
}
catch { }
finally
{
cmd.Dispose();
if (conn.State = ConnectionState.Open)
conn.Close();
}
}
}
上面建立的BindData類中有2個建構函式,分別用於綁定分類、綁定Arhive的不同形式。如果使用其他資料繫結控制項則可進行相應修改;
建立了1個私人方法BindDataToRepeater用於把對應的SQL語句查詢的結果綁定到對應的Repeater控制項上。同時在此方法中使用了SqlDataReader,以提高綁定資料的速度。如果你使用了資料處理站可修改BindDataToRepeater中的具體實現過程;
2個共有方法BindCategory和BindArchive分別用於建立不同SQL語句、設定Repater的ID;
同時需要引入System.Web.UI、System.Web.UI.HtmlControls、System.Data.SqlClient3個必要的命名空間。
值得注意的是在BindDataToRepeater方法中使用了try..catch語句,但並沒有在catch塊中做任何事情,為什麼我們用try.catch卻不在catch塊中做點什麼事情呢,不是多此一舉嗎?使用try..catch是為了防止在執行BindDataToRepeater時拋出異常,若此處出現異常且此方法是在多線程中執行的,將會導致IIS進程崩潰,進而影響其他頁面的正常執行,故而用try...catch防止BindDataToRepeater拋出錯誤。
我們之所以為資料繫結建立一個類,是為了提高記憶體利用率,當資料載入(Load)完畢的時候,為這個類建立的執行個體就會銷毀。我們也可以通過在頁面中建立幾個全域變數來實現。但我還是建議以類的形式傳遞資料而不是使用全域變數。下面,我們開始在頁面的Load中建立線程了。首先你需要在頁面中引入System.Threading命名空間。
public classBindData
{
private int currentPage = 1;
private Repeater rpID;
public BindData(Repeater rpID)
{
this.rpID = rpID;
}
public BindData(Repeater rpID,int page)
{
this.rpID = rpID;
this.currentPage = page;
}
public void BindCategory()
{
string strSql="SELECT * FROM Category";
this.BindDataToRepeater(strSql, this.rpID);
}
public void BindArchive()
{
string strSql = string.Format("SELECT * FROM Archives WHERE Page={0}",this.currentPage);
this.BindDataToRepeater(strSql, this.rpID);
}
private void BindDataToRepeater(string strSql, Repeater rp)
{
if (rp == null) return;
SqlConnection conn = new SqlConnection("data source=資料服務器地址;User ID=使用者名稱;pwd=密碼;Initial Catalog=資料庫名");
SqlCommand cmd = new SqlCommand(strSql, conn);
SqlDataReader dtr;
try
{
conn.Open();
dtr = cmd.ExecuteReader();
controlID.DataSource = rp;
controlID.DataBind();
if (!dtr.IsClosed)
dtr.Close();
}
catch { }
finally
{
cmd.Dispose();
if (conn.State = ConnectionState.Open)
conn.Close();
}
}
}
上面的代碼顯示在!IsPostBack狀態下綁定資料。利用Request.QueryString擷取了當前頁碼,並建立了BindData的2個執行個體LoadArchives、LoadCategory,通過 Thread thArhives=new Thread(new ThreadStart(LoadArchives.BindArchive))為綁定Arhice建立線程,通過Thread thCategory = new Thread(new ThreadStart(LoadCategory.BindCategory))為綁定分類建立線程,同時調用Thread的Start方法使2個線程進入執行狀態。最後,在Load的最下面用Thread的Join方法使建立的2個線程與頁面載入同步。
值得注意的是,Join方法是必須的,如果不使用,可能導致建立的線程還未把資料完全綁定到Repeater上,Load就已經執行完畢,若如此頁面上將沒有任何資料。同時調用Start的程式碼應盡量早,調用Join的程式碼都應盡量遲——盡量放在Page_Load程式碼片段的末尾,這樣才能達到多線程的目的,若你每調用一個Start馬上調用Join,其實質和沒有使用多線程的效果是一樣的。Join在MSND上的解釋是:在繼續執行標準的 COM 和 SendMessage 訊息泵處理期間,阻塞調用線程,直到某個線程終止為止。
只要設定好ASPX頁面Repeater的繫結項目,資料就可成功載入了。上面僅僅展示了2個SQL語句的查詢,如果你有10個或者更多的SQL查詢,在Page_Load中建立10個線程,讓他們非同步執行,最後用Join同步到Load,是一個提高效能的不錯方法。
本ASP.Net教程引用來源: http://www.goberl.com/archive/item59.aspx