網絡應用程序的一般都會或多或少的使用到線程,甚至可以說,一個功能稍微強大的網絡應用程序總會在其中開出或多或少的線程,如果應用程序中開出的線程數目大於二個,那麼就可以把這個程序稱之為多線程應用程序。那麼為什麼在網絡應用程序總會和線程交纏在一起呢?這是因為網絡應用程序在執行的時候,會遇到很多意想不到的問題,其中最常見的是網絡阻塞和網絡等待等。
程序在處理這些問題的時候往往需要花費很多的時間,如果不使用線程,則程序在執行時的就會表現出如運行速度慢,執行時間長,容易出現錯誤、反應遲鈍等問題。而如果把這些可能造成大量占用程序執行時間的過程放在線程中處理,就往往能夠大大提高應用程序的運行效率和性能和獲得更優良的可伸縮性。那麼這是否就意味著應該在網絡應用程序中廣泛的使用線程呢?情況並非如此,線程其實是一把雙刃劍,如果不分場合,在不需要使用的地方強行使用就可能會產生許多程序垃圾,或者在程序結束後,由於沒有能夠銷毀創建的進程而導致應用程序掛起等問題。
所以如果你認為自己編寫的代碼足夠快,那我給你的建議還是別使用線程或多線程。這裡要提醒諸位的是如果您對在Windows下的線程和其執行原理和機制還不十分清楚,可以先參閱一下介紹Windows操作系統方面的書籍,它們一般都會對其進行比較詳細的闡述。然後再閱讀本文。
一.簡介在Visual C#中創建和使用線程:
Visual C#中使用的線程都是通過自命名空間System.Threading中的Thread類經常實例化完成的。通過Thread類的構造函數來創建可供Visual C#使用的線程,通過Thread中的方法和屬性來設定線程屬性和控制線程的狀態。以下Thread類中的最典型的構造函數語法,在Visual C#中一般使用這個構造函數來創建、初始化Thread實例。
public Thread (
ThreadStart start
) ;
參數
start ThreadStart 委托,它將引用此線程開始執行時要調用的方法。
Thread還提供了其他的構造函數來創建線程,這裡就不一一介紹了。表01是Thread類中的一些常用的方法及其簡要說明:
方法 說明 Abort 調用此方法通常會終止線程,但會引起ThreadAbortException類型異常。 Interrupt 中斷處於WaitSleepJoin 線程狀態的線程。 Join 阻塞調用線程,直到某個線程終止時為止。 ResetAbort 取消當前線程調用的Abor方法。 Resume 繼續已掛起的線程。 Sleep 當前線程阻塞指定的毫秒數。 Start 操作系統將當前實例的狀態更改為ThreadState.Running。 Suspend 掛起線程,或者如果線程已掛起,則不起作用。 表01:Thread類的常用方法及其說明
這裡要注意的是在.Net中執行一個線程,當線程執行完畢後,一般會自動銷毀。如果線程沒有自動銷毀可通過Thread中的Abort方法來手動銷毀,但同樣要注意的是如果線程中使用的資源沒有完全銷毀,Abort方法執行後,也不能保證線程被銷毀。在Thread類中還提供了一些屬性用以設定和獲取創建的Thread實例屬性,表02中是Thread類的一些常用屬性及其說明:
屬性 說明 CurrentCulture 獲取或設置當前線程的區域性。 CurrentThread 獲取當前正在運行的線程。 IsAlive 獲取一個值,該值指示當前線程的執行狀態。 IsBackground 獲取或設置一個值,該值指示某個線程是否為後台線程。 Name 獲取或設置線程的名稱。 Priority 獲取或設置一個值,該值指示線程的調度優先級。 ThreadState 獲取一個值,該值包含當前線程的狀態。 表02:Thread類的常用屬性及其說明
二.本文的主要內容及程序調試和運行環境:
本文的主要內容是介紹多線程給用Visual C#編寫網絡應用程序帶來的更高性能提高。具體的做法是在Visual C#用二種不同的方法,一種采用了多線程,另一種不是,來實現同一個具體網絡應用示例,此示例的功能是獲取網絡同一網段多個IP地址對應的計算機的在線狀態和對應的計算機名稱,通過比較這二種方法的不同執行效率就可知多線程對提高網絡應用程序的執行效率是多麼的重要了。以下是本文中設計到程序的調試和運行的基本環境配置:
(1).微軟公司視窗2000服務器版。
(2).Visual Studio .Net 2002正式版,.Net FrameWork SDK版本號3705。
三.掃描網絡計算機的原理:
下面介紹的這個示例的功能是通過掃描一個給定區間IP地址,來判斷這些IP地址對應的計算機是否在線,如果在線則獲得IP地址對應的計算機名稱。程序判斷計算機是否在線的是采用對給定IP地址的計算機進行DNS解析,如果能夠根據IP地址解析出對應的計算機名稱,則說明此IP地址對應的計算機在線;反之,如果解析不出,則會產生異常出錯,通過對異常的捕獲,得到此IP地址對應的計算機並不在線。
為了更清楚地說明問題和便於掌握在Visual C#編寫多線程網絡應用程序的方法,本文首先介紹的是不基於多線程的網絡計算機掃描程序的編寫步驟,然後再在其基礎上,把它修改成多線程的計算機掃描程序,最後比較這二個程序的執行效率,你就會發現線程在網絡編程中的重要作用了。
四.Visual C#實現不基於多線程的網絡計算機掃描程序
以下是在Visual C#實現不基於多線程的網絡計算機掃描程序步驟:
1. 啟動Visual Studio .Net,並新建一個Visual C#項目,項目名稱為【掃描網絡計算機】。
2. 把Visual Studio .Net的當前窗口切換到【Form1.cs(設計)】窗口,並從【工具箱】中的【Windows窗體組件】選項卡中往Form1窗體中拖入下列組件,並執行相應操作:
四個NumericUpDown組件,用以組合成一個IP地址區間。
一個ListBox組件,用以顯示掃描後的結果。
一個ProgressBar組件,用以顯示程序的運行進度。
四個Label組件,用以顯示提示信息。
一個GroupBox組件。
一個Button組件,名稱為button1,並在這組件拖入窗體後,雙擊button1,這樣Visual Studio .Net就會產生這button1組件Click事件對應的處理代碼。
界面設置如下圖:
圖01:【掃描網絡計算機】項目的設計界面
3. 把Visual Studio .Net的當前窗口切換到【Form1.cs】,進入Form1.cs文件的編輯界面。在Form1.cs頭部,用下列代碼替換系統缺省的導入命名空間代碼:
using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
using System.Net.Sockets ;
using System.Net ;
4. 用下列代碼替換Form1.cs中的button1的Click時間對應的處理代碼,下列代碼的功能是掃描給定的IP地址區間,並把掃描結果顯示出來。
private void button1_Click ( object sender , System.EventArgs e )
{
listBox1.Items.Clear ( ) ;
//清楚掃描結果顯示區域
DateTime StartTime = DateTime.Now ;
//獲取當前時間
string mask = numericUpDown1.Value.ToString ( ) + "." + numericUpDown2.Value.ToString ( ) +
"." + numericUpDown3.Value.ToString ( ) + "." ;
int Min = ( int ) numericUpDown4.Value ;
int Max = ( int ) numericUpDown5.Value ;
if ( Min > Max )
{
MessageBox.Show ( "輸入的IP地址區間不合法,請檢查!" , "錯誤!" ) ;
return ;
}
//判斷輸入的IP地址區間是否合法
progressBar1.Minimum = Min ;
progressBar1.Maximum = Max ;
int i ;
for ( i = Min ; i <= Max ; i++ )
{
string ip= mask + i.ToString ( ) ;
IPAddress myIP = IPAddress.Parse ( ip ) ;
//根據給定的IP地址字符串,處境IPAddress實例
try
{
IPHostEntry myHost = Dns.GetHostByAddress ( myIP ) ;
string HostName = myHost.HostName.ToString ( ) ;
listBox1.Items.Add ( ip + "名稱為:" + HostName ) ;
}
catch
{
listBox1.Items.Add ( ip + "主機沒有響應!" ) ;
}
progressBar1.Value = i ;
}
//掃描給定IP地址對應的計算機是否在線
DateTime EndTime = DateTime.Now ;
TimeSpan ts = EndTime-StartTime ;
//獲得掃描網絡計算機所使用的時間
label4.Text = ts.Seconds.ToString ( ) + "秒" ;
MessageBox.Show ( "成功完成檢測!" , "提示" ) ;
progressBar1.Value = Min ;
}
由於上述代碼比較簡單,並且在代碼中的注釋也比較詳細,這裡就不加以解釋了,但請注意上面代碼中對時間日期類型數據的處理方法。因為有很多人曾經向我訊問過類似問題。
5. 至此,不基於多線程的【掃描網絡計算機】項目的全部工作就完成了,程序的執行是很機械的,其方法是對每一個IP按照順序進行DNS解析,並得到解析結果,所以程序的執行時間和掃描的IP地址區間段大小成正比。圖02是此程序運行後,掃描"10.138.198.1"至"10.138.198.10"這個IP地址區間計算機後的運行界面。整個程序的運行時間為43秒:
圖02:不基於多線程的【掃描網絡計算機】項目的運行界面
五.把【掃描網絡計算機】程序修改成基於多線程的程序:
在修改成多線程程序之前,必須面對並解決下面幾個問題:
1. 線程是無返回值的,所以在線程中處理、調用的應是一個過程,所以要把掃描IP地址對應的計算機的代碼給包裝成一個過程。
2. 放在線程中處理的過程,因為沒有返回值,從而無法向主程序(進程)傳遞數值。但掃描IP地址對應的計算機的過程卻要向主程序(進程)傳遞IP地址是否在線的數據,所以在修改成多線程程序之前,必須從線程往主程序(進程)傳遞數據的問題。
下面是在【掃描網絡計算機】項目的基礎上,把它修改成基於多線程程序的具體實現步驟:
1. 由於程序中使用到線程,所以在Form1.cs代碼首部,導入命名空間代碼區中加入下列代碼,下列代碼是導入Thread類所在的命名空間。
using System.Threading ;
2. 在Form1.cs代碼的namespace代碼區加入下列語句,下列語句是定義一個delegate:
public delegate void UpdateList ( string sIP , string sHostName ) ;
3. 在Form1.cs中的定義Form1的class代碼區定義加入下列代碼,下列代碼是定義一個變量,用以存放程序執行的時間:
private System.DateTime StartTime ;
4. 在Form1.cs代碼的Main函數之後,添加下列代碼,下列代碼是創建一個名稱為ping的Class,這個Class能夠通過其設定的屬性接收給定的IP地址字符串,並據此來判斷此IP地址字符串對應的計算機是否在線,並通過其設定的HostName屬性接收從線程傳遞來的數據。
public class ping
{
public UpdateList ul ;
public string ip ;
//定義一個變量,用以接收傳送來的IP地址字符串
public string HostName ;
//定義一個變量,用以向主進展傳遞對應IP地址是否在線數據
public void scan ( )
{
IPAddress myIP = IPAddress.Parse ( ip ) ;
try
{
IPHostEntry myHost = Dns.GetHostByAddress ( myIP );
HostName = myHost.HostName.ToString ( ) ;
}
catch
{
HostName = "" ;
}
if (HostName == "")
HostName = " 主機沒有響應!";
if ( ul != null)
ul ( ip , HostName ) ;
}
//定義一個過程(也可以看出為方法),用以判斷傳送來的IP地址對應計算機是否在線
}
5. 在Form1.cs中添加完上述代碼後,再添加下列代碼:
void UpdateMyList ( string sIP , string sHostName )
{
lock ( listBox1 )
{
listBox1.Items.Add ( sIP + " " + sHostName ) ;
if ( progressBar1.Value != progressBar1.Maximum )
{
progressBar1.Value++ ;
}
if ( progressBar1.Value == progressBar1.Maximum )
{
MessageBox.Show ( "成功完成檢測!" , "提示" ) ;
DateTime EndTime = DateTime.Now ;
TimeSpan ts = EndTime-StartTime ;
label4.Text = ts.Seconds.ToString ( ) + "秒" ;
//顯示掃描計算機所需要的時間
progressBar1.Value = progressBar1.Minimum ;
}
}
}
6. 用下列代碼替換Form1.cs中button1的Click事件對應的處理代碼,下列代碼功能是創建多個掃描給定IP地址區間對應的計算機線程實例,並顯示掃描結果。
private void button1_Click(object sender, System.EventArgs e)
{
listBox1.Items.Clear ( ) ;
//清楚掃描結果顯示區域
StartTime = DateTime.Now ;
//獲取當前時間
string mask = numericUpDown1.Value.ToString ( ) + "." + numericUpDown2.Value.ToString ( ) +
"." + numericUpDown3.Value.ToString ( ) + "." ;
int Min = ( int ) numericUpDown4.Value ;
int Max = ( int ) numericUpDown5.Value ;
if ( Min > Max )
{
MessageBox.Show ( "輸入的IP地址區間不合法,請檢查!" , "錯誤!" ) ;
return ;
}
//判斷輸入的IP地址區間是否合法
int _ThreadNum = Max - Min + 1 ;
Thread[] mythread = new Thread [ _ThreadNum ] ;
//創建一個多個Thread實例
progressBar1.Minimum = Min ;
progressBar1.Maximum = Max + 1 ;
progressBar1.Value = Min ;
int i ;
for (i = Min ; i <= Max ; i++ )
{
int k = Max - i ;
ping HostPing = new ping ( ) ;
//創建一個ping實例
HostPing.ip = mask + i.ToString ( ) ;
HostPing.ul = new UpdateList ( UpdateMyList ) ;
//向這個ping實例中傳遞IP地址字符串
mythread[k] = new Thread ( new ThreadStart ( HostPing.scan ) ) ;
//初始化一個線程實例
mythread[k].Start ( ) ;
//啟動線程
}
}
至此,【掃描網絡計算機】項目已經被修改成一個多線程的程序了,此時在運行程序,並且同樣再掃描上面給定IP地址區間對應的計算機,就會驚奇的發現程序執行時間所建為10秒了,並且不論要掃描的計算機數目有多少,程序的運行時間也是10秒左右,這是因為程序為掃描每一個IP都分配一個線程,這樣程序的執行時間就不與要掃描的IP地址段中的IP地址數目有關聯了,這樣也就大大減少了程序的運行時間,提高了程序的運行效率,這也充分體現出多線程給網絡編程帶來的好處。圖03也是程序掃描"10.138.198.1"至"10.138.198.10"這個IP地址區間計算機後的運行界面所示:
圖03:基於多線程的【掃描網絡計算機】項目的運行界面
通過對二個程序的比較可見,在編寫網絡應用程序中,正確的使用線程的確能夠大大提高程序的運行效率。
六.總結:
至此,本節要介紹的內容就全部結束了,不知道諸位通過上面的介紹是否了解、掌握了下面幾點:
1. 如何獲取系統當前時間,和實現時間日期類型數據的加減。
2. 在編寫網絡應用程序時候,使用線程(多線程)的原因,以及線程(多線程)會給網絡應用程序帶來什麼好處。
3. 如何在應用程序中創建多個線程實例。
4. 如何實現帶"返回值"的線程。
如果上述要點你都能夠掌握,那是再好不過的了。但如果你對線程及其使用方法還感覺模糊,那也不要緊,畢竟線程在編程技術中是一個內容豐富,使用復雜的東東,要立馬掌握的確是很困難的事情。在以後的文章中也將再介紹這方面的內容。