額,我猜你現在可能會這么想“既然線程會對我的程序產生負面影響,那么我為什么要使用它呢?”。其實問題的關鍵不在於到底用不用線程,而在於何時何地使用線程。知道在什么情況下應該使用線程是好的設計決策的核心。使用線程有兩個不同的優勢。在這一部分,我們將討論這兩個優勢是什么。
后台處理邏輯
第一個使用線程的優勢是當你需要在后台運行一個很耗時的操作同時希望用戶界面仍然可用時。我們都遇到過很多次由於后台在查詢數據或者在處理大量信息而導致界面沒有響應的情況。例如,專業圖形處理軟件以一個特定格式顯示圖片時。在一些產品的早期版本,讓程序顯示一個大的圖片會導致程序沒有響應直到圖片顯示出來。有時候為了完成某項工作,我們不得不讓它晚上來處理圖片,然后第二天早上來看我們期望的結果,這樣做也是由於在電腦面前空坐一個小時忒不靠譜。這個問題在於提供一個后台線程來做一些CPU敏感工作同時讓你的用戶界面運行在主線程中。
我們來看一個需要生成新線程來處理的后台操作。這個例子主要是用來做文件搜索的。當搜索引擎找到一個匹配的文件后,它會向列表中添加一個新值。
下面的代碼顯示這個方法確實需要生成新線程:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o guofoo@163.com * */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using System.Threading; namespace SearchForm { public partial class Form1 : Form { string search; string[] args = new string[1]; delegate void UpdateData(string returnVal); UpdateData UIDel; public Form1() { InitializeComponent(); } private void cmdSingle_Click(object sender, EventArgs e) { Search(); } public void Search() { search = textBox1.Text; SearchDirectory(@"D:\Pictures"); } public void SearchDirectory(string path) { //Search the directory DirectoryInfo di = new DirectoryInfo(path); FileInfo[] fi = di.GetFiles(search); foreach (FileInfo myFile in fi) { args[0] = myFile.FullName; if (UIDel != null) { this.Invoke(UIDel, args); } else { UpdateUI(args[0]); } } //Search its sub directories. DirectoryInfo[] subDirs = di.GetDirectories(); foreach (DirectoryInfo myDir in subDirs) { SearchDirectory(myDir.FullName); } } private void cmdMultiple_Click(object sender, EventArgs e) { UIDel = new UpdateData(UpdateUI); Thread t = new Thread(new ThreadStart(Search)); t.Start(); } private void UpdateUI(string result) { lstPrime.Items.Add(result); } } }
編譯上面的例子然后運行。在搜索框輸入一個搜索條件,比如*.*, 單擊單線程搜索按鈕,然后觀察結果。你會發現,我們在搜索文件同時試着在每次發現匹配的文件時向界面更新一次。然而,由於用戶界面和搜索程序代碼運行在同一個線程中,我們在搜索代碼完成之前看不到任何更新。我們也無法在搜索過程中重新設定窗體尺寸大小。
這個很長的代碼片段實際上是一個非常簡單的場景。我們來看一下能否通過一個簡單的的改動來解決這個問題。在多線程搜索按鈕中添加下面代碼並使用一個新線程來搜索:
private void cmdMultiple_Click(object sender, EventArgs e) { UIDel = new UpdateData(UpdateUI); Thread t = new Thread(new ThreadStart(Search)); t.Start(); }
我們再次編譯運行上面的例子。輸入同樣的搜索條件然后單擊多線程搜索按鈕。你可以看到有一些不同的地方-這次我們的輸出結果會立即顯示出來。這是因為Windows現在會在后台搜索線程和前台顯示線程之間進行切換。處理器現在給了界面顯示程序獨立執行時間。你也會發現在搜索過程中可以重新設定窗體大小。
也可能有其他導致我們的用戶接口沒有響應的后台程序。我們可能還有一些更消耗CPU 資源的處理過程,比如對數據庫執行搜索,排序,格式化,截取以及過濾數據功能。這是使用多線程的另外一種情況。你也可能生成一個單獨線程進行日志處理。在這些情況下用戶接口不會卡死,但是如果不是在它自己線程(進行日志操作)里的話會顯得稍微有點慢。
訪問外部資源
第二種可能要用到線程的情況是當你需要訪問一些非本地的資源時。可能是網絡上的一個數據庫文件或者是網絡共享文件之類的。在這種情況下,網絡性能會極大地影響你的程序的性能。
讓我們來看一個例子。在這個例子中我們將連接一個數據庫。假設網絡性能很差導致我們的程序性能也很不好。同時也假定公司安全策略規定在遠程數據庫服務器上不可以安裝任何應用程序。
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:Daniel Dong * Blog: www.cnblogs.com/danielWise * Email: guofoo@163.com * */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Data.SqlClient; using System.Threading; namespace Chapter02 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void cmdQuery_Click(object sender, EventArgs e) { QueryData(); } public void QueryData() { SqlDataReader objReader; SqlConnection objConn; SqlCommand objCommand; int intEmployeeID; string strFirstName; string strTitle; int intReportsTo; objConn = new SqlConnection("Data Source=.;Initial Catalog=Northwind;Integrated Security=True"); objCommand = new SqlCommand("SELECT EmployeeID, FirstName, Title, ReportsTo FROM Employees", objConn); objConn.Open(); objReader = objCommand.ExecuteReader(CommandBehavior.CloseConnection); while (objReader.Read()) { intEmployeeID = objReader.GetInt32(0); strFirstName = objReader.GetString(1); strTitle = objReader.GetString(2); if (objReader.IsDBNull(3)) { intReportsTo = 0; } else { intReportsTo = objReader.GetInt32(3); } listBox1.Items.Add(intEmployeeID.ToString() + " " + strFirstName + " " + strTitle + " " + intReportsTo.ToString()); } objReader.Close(); objConn.Close(); } } }
你可以從這個例子中發現我們所做的就是查詢一個數據庫(本例中為了方便使用本地數據庫,實際應用時一般是遠程數據庫)。返回的數據不是很多,但是你會發現用戶界面會在它花費時間獲取數據並更新到列表中得這段時間內卡住。我們仍然通過另外生成一個線程並在這個新的線程中執行數據庫查詢來改進這個程序。現在再添加一個新的按鈕並加入以下代碼:
private void cmdMultiQuery_Click(object sender, EventArgs e) { Thread t = new Thread(new ThreadStart(QueryData)); t.Start(); }
現在我們運行這段代碼,會得到與我們上個例子的結果類似的結果:
我們也可以在查詢過程中重設窗口尺寸。用戶界面在整個查詢過程中都是相應的。當然,我想重申的是以上內容並不意味着你應該在每次訪問數據庫時都創建一個新線程。你應該先分析一下能否把數據庫文件或者程序移動到同一台服務器上。同時確定這個組件不會被不同的客戶端程序持續地調用。如果是這樣的話那么就得為每次調用生成一個新線程且會比你期望的消耗更多資源。當每次調用我們的對象時不用都生成新線程,有些時候可以重用對象以及線程。這個問題將會在第三章和第五章介紹。
下一篇介紹線程陷阱以及循環中的線程...


![UY0]EID$$6M1[I_N_~WPQ%8 UY0]EID$$6M1[I_N_~WPQ%8](/image/aHR0cHM6Ly9pbWFnZXMuY25ibG9ncy5jb20vY25ibG9nc19jb20vZGFuaWVsV2lzZS8yMDEyMDEvMjAxMjAxMTcyMTIzNDE0ODk2LmpwZw==.png)