網(wǎng)絡(luò)應(yīng)用程序的一般都會或多或少的使用到線程,甚至可以說,一個功能稍微強大的網(wǎng)絡(luò)應(yīng)用程序總會在其中開出或多或少的線程,如果應(yīng)用程序中開出的線程數(shù)目大于二個,那么就可以把這個程序稱之為多線程應(yīng)用程序。那么為什么在網(wǎng)絡(luò)應(yīng)用程序總會和線程交纏在一起呢?這是因為網(wǎng)絡(luò)應(yīng)用程序在執(zhí)行的時候,會遇到很多意想不到的問題,其中最常見的是網(wǎng)絡(luò)阻塞和網(wǎng)絡(luò)等待等。
程序在處理這些問題的時候往往需要花費很多的時間,如果不使用線程則程序在執(zhí)行時的就會表現(xiàn)出如運行速度慢,執(zhí)行時間長,容易出現(xiàn)錯誤、反應(yīng)遲鈍等問題。而如果把這些可能造成大量占用程序執(zhí)行時間的過程放在線程中處理,就往往能夠大大提高應(yīng)用程序的運行效率和性能和獲得更優(yōu)良的可伸縮性。那么這是否就意味著應(yīng)該在網(wǎng)絡(luò)應(yīng)用程序中廣泛的使用線程呢?情況并非如此,線程其實是一把雙刃劍,如果不分場合,在不需要使用的地方強行使用就可能會產(chǎn)生許多程序垃圾,或者在程序結(jié)束后,由于沒有能夠銷毀創(chuàng)建的進(jìn)程而導(dǎo)致應(yīng)用程序掛起等問題。
所以如果你認(rèn)為自己編寫的代碼足夠快,那我給你的建議還是別使用線程或多線程。這里要提醒諸位的是如果您對在windows下的線程和其執(zhí)行原理和機制還不十分清楚,可以先參閱一下介紹windows操作系統(tǒng)方面的書籍,它們一般都會對其進(jìn)行比較詳細(xì)的闡述。然后再閱讀本文。
一.簡介在visual c#中創(chuàng)建和使用線程:
visual c#中使用的線程都是通過自命名空間system.threading中的thread類經(jīng)常實例化完成的。通過thread類的構(gòu)造函數(shù)來創(chuàng)建可供visual c#使用的線程,通過thread中的方法和屬性來設(shè)定線程屬性和控制線程的狀態(tài)。以下thread類中的最典型的構(gòu)造函數(shù)語法,在visual c#中一般使用這個構(gòu)造函數(shù)來創(chuàng)建、初始化thread實例。
public thread (
threadstart start
) ;
參數(shù)
start threadstart 委托,它將引用此線程開始執(zhí)行時要調(diào)用的方法。
thread還提供了其他的構(gòu)造函數(shù)來創(chuàng)建線程,這里就不一一介紹了。表01是thread類中的一些常用的方法及其簡要說明:
表01:thread類的常用方法及其說明方法 說明 abort 調(diào)用此方法通常會終止線程,但會引起threadabortexception類型異常。 interrupt 中斷處于waitsleepjoin 線程狀態(tài)的線程。 join 阻塞調(diào)用線程,直到某個線程終止時為止。 resetabort 取消當(dāng)前線程調(diào)用的abor方法。 resume 繼續(xù)已掛起的線程。 sleep 當(dāng)前線程阻塞指定的毫秒數(shù)。 start 操作系統(tǒng)將當(dāng)前實例的狀態(tài)更改為threadstate.running。 suspend 掛起線程,或者如果線程已掛起,則不起作用。
這里要注意的是在.net中執(zhí)行一個線程,當(dāng)線程執(zhí)行完畢后,一般會自動銷毀。如果線程沒有自動銷毀可通過thread中的abort方法來手動銷毀,但同樣要注意的是如果線程中使用的資源沒有完全銷毀,abort方法執(zhí)行后,也不能保證線程被銷毀。在thread類中還提供了一些屬性用以設(shè)定和獲取創(chuàng)建的thread實例屬性,表02中是thread類的一些常用屬性及其說明:
表02:thread類的常用屬性及其說明屬性 說明 currentculture 獲取或設(shè)置當(dāng)前線程的區(qū)域性。 currentthread 獲取當(dāng)前正在運行的線程。 isalive 獲取一個值,該值指示當(dāng)前線程的執(zhí)行狀態(tài)。 isbackground 獲取或設(shè)置一個值,該值指示某個線程是否為后臺線程。 name 獲取或設(shè)置線程的名稱。 priority 獲取或設(shè)置一個值,該值指示線程的調(diào)度優(yōu)先級。 threadstate 獲取一個值,該值包含當(dāng)前線程的狀態(tài)。
二.本文的主要內(nèi)容及程序調(diào)試和運行環(huán)境:
本文的主要內(nèi)容是介紹多線程給用visual c#編寫網(wǎng)絡(luò)應(yīng)用程序帶來的更高性能提高。具體的做法是在visual c#用二種不同的方法,一種采用了多線程,另一種不是,來實現(xiàn)同一個具體網(wǎng)絡(luò)應(yīng)用示例,此示例的功能是獲取網(wǎng)絡(luò)同一網(wǎng)段多個ip地址對應(yīng)的計算機的在線狀態(tài)和對應(yīng)的計算機名稱,通過比較這二種方法的不同執(zhí)行效率就可知多線程對提高網(wǎng)絡(luò)應(yīng)用程序的執(zhí)行效率是多么的重要了。以下是本文中設(shè)計到程序的調(diào)試和運行的基本環(huán)境配置:
(1).微軟公司視窗2000服務(wù)器版。
(2).visual studio .net 2002正式版,.net framework sdk版本號3705。
三.掃描網(wǎng)絡(luò)計算機的原理:
下面介紹的這個示例的功能是通過掃描一個給定區(qū)間ip地址,來判斷這些ip地址對應(yīng)的計算機是否在線,如果在線則獲得ip地址對應(yīng)的計算機名稱。程序判斷計算機是否在線的是采用對給定ip地址的計算機進(jìn)行dns解析,如果能夠根據(jù)ip地址解析出對應(yīng)的計算機名稱,則說明此ip地址對應(yīng)的計算機在線;反之,如果解析不出,則會產(chǎn)生異常出錯,通過對異常的捕獲,得到此ip地址對應(yīng)的計算機并不在線。
為了更清楚地說明問題和便于掌握在visual c#編寫多線程網(wǎng)絡(luò)應(yīng)用程序的方法,本文首先介紹的是不基于多線程的網(wǎng)絡(luò)計算機掃描程序的編寫步驟,然后再在其基礎(chǔ)上,把它修改成多線程的計算機掃描程序,最后比較這二個程序的執(zhí)行效率,你就會發(fā)現(xiàn)線程在網(wǎng)絡(luò)編程中的重要作用了。
四.visual c#實現(xiàn)不基于多線程的網(wǎng)絡(luò)計算機掃描程序
以下是在visual c#實現(xiàn)不基于多線程的網(wǎng)絡(luò)計算機掃描程序步驟:
1. 啟動visual studio .net,并新建一個visual c#項目,項目名稱為【掃描網(wǎng)絡(luò)計算機】。
2. 把visual studio .net的當(dāng)前窗口切換到【form1.cs(設(shè)計)】窗口,并從【工具箱】中的【windows窗體組件】選項卡中往form1窗體中拖入下列組件,并執(zhí)行相應(yīng)操作:
四個numericupdown組件,用以組合成一個ip地址區(qū)間。
一個listbox組件,用以顯示掃描后的結(jié)果。
一個progressbar組件,用以顯示程序的運行進(jìn)度。
四個label組件,用以顯示提示信息。
一個groupbox組件。
一個button組件,名稱為button1,并在這組件拖入窗體后,雙擊button1,這樣visual studio .net就會產(chǎn)生這button1組件click事件對應(yīng)的處理代碼。
界面設(shè)置如下圖:
圖01:【掃描網(wǎng)絡(luò)計算機】項目的設(shè)計界面
3. 把visual studio .net的當(dāng)前窗口切換到【form1.cs】,進(jìn)入form1.cs文件的編輯界面。在form1.cs頭部,用下列代碼替換系統(tǒng)缺省的導(dǎo)入命名空間代碼: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時間對應(yīng)的處理代碼,下列代碼的功能是掃描給定的ip地址區(qū)間,并把掃描結(jié)果顯示出來。private void button1_click ( object sender , system.eventargs e )
{
listbox1.items.clear ( ) ;
//清楚掃描結(jié)果顯示區(qū)域
datetime starttime = datetime.now ;
//獲取當(dāng)前時間
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地址區(qū)間不合法,請檢查!" , "錯誤!" ) ;
return ;
}
//判斷輸入的ip地址區(qū)間是否合法
progressbar1.minimum = min ;
progressbar1.maximum = max ;
int i ;
for ( i = min ; i <= max ; i++ )
{
string ip= mask + i.tostring ( ) ;
ipaddress myip = ipaddress.parse ( ip ) ;
//根據(jù)給定的ip地址字符串,處境ipaddress實例
try
{
iphostentry myhost = dns.gethostbyaddress ( myip ) ;
string hostname = myhost.hostname.tostring ( ) ;
listbox1.items.add ( ip + "名稱為:" + hostname ) ;
}
catch
{
listbox1.items.add ( ip + "主機沒有響應(yīng)!" ) ;
}
progressbar1.value = i ;
}
//掃描給定ip地址對應(yīng)的計算機是否在線
datetime endtime = datetime.now ;
timespan ts = endtime-starttime ;
//獲得掃描網(wǎng)絡(luò)計算機所使用的時間
label4.text = ts.seconds.tostring ( ) + "秒" ;
messagebox.show ( "成功完成檢測!" , "提示" ) ;
progressbar1.value = min ;
}
由于上述代碼比較簡單,并且在代碼中的注釋也比較詳細(xì),這里就不加以解釋了,但請注意上面代碼中對時間日期類型數(shù)據(jù)的處理方法。因為有很多人曾經(jīng)向我訊問過類似問題。
5. 至此,不基于多線程的【掃描網(wǎng)絡(luò)計算機】項目的全部工作就完成了,程序的執(zhí)行是很機械的,其方法是對每一個ip按照順序進(jìn)行dns解析,并得到解析結(jié)果,所以程序的執(zhí)行時間和掃描的ip地址區(qū)間段大小成正比。圖02是此程序運行后,掃描"10.138.198.1"至"10.138.198.10"這個ip地址區(qū)間計算機后的運行界面。整個程序的運行時間為43秒:
圖02:不基于多線程的【掃描網(wǎng)絡(luò)計算機】項目的運行界面
五.把【掃描網(wǎng)絡(luò)計算機】程序修改成基于多線程的程序:
在修改成多線程程序之前,必須面對并解決下面幾個問題:
1. 線程是無返回值的,所以在線程中處理、調(diào)用的應(yīng)是一個過程,所以要把掃描ip地址對應(yīng)的計算機的代碼給包裝成一個過程。
2. 放在線程中處理的過程,因為沒有返回值,從而無法向主程序(進(jìn)程)傳遞數(shù)值。但掃描ip地址對應(yīng)的計算機的過程卻要向主程序(進(jìn)程)傳遞ip地址是否在線的數(shù)據(jù),所以在修改成多線程程序之前,必須從線程往主程序(進(jìn)程)傳遞數(shù)據(jù)的問題。
下面是在【掃描網(wǎng)絡(luò)計算機】項目的基礎(chǔ)上,把它修改成基于多線程程序的具體實現(xiàn)步驟:
1. 由于程序中使用到線程,所以在form1.cs代碼首部,導(dǎo)入命名空間代碼區(qū)中加入下列代碼,下列代碼是導(dǎo)入thread類所在的命名空間。using system.threading ;
2. 在form1.cs代碼的namespace代碼區(qū)加入下列語句,下列語句是定義一個delegate:public delegate void updatelist ( string sip , string shostname ) ;
3. 在form1.cs中的定義form1的class代碼區(qū)定義加入下列代碼,下列代碼是定義一個變量,用以存放程序執(zhí)行的時間:private system.datetime starttime ;
4. 在form1.cs代碼的main函數(shù)之后,添加下列代碼,下列代碼是創(chuàng)建一個名稱為ping的class,這個class能夠通過其設(shè)定的屬性接收給定的ip地址字符串,并據(jù)此來判斷此ip地址字符串對應(yīng)的計算機是否在線,并通過其設(shè)定的hostname屬性接收從線程傳遞來的數(shù)據(jù)。public class ping
{
public updatelist ul ;
public string ip ;
//定義一個變量,用以接收傳送來的ip地址字符串
public string hostname ;
//定義一個變量,用以向主進(jìn)展傳遞對應(yīng)ip地址是否在線數(shù)據(jù)
public void scan ( )
{
ipaddress myip = ipaddress.parse ( ip ) ;
try
{
iphostentry myhost = dns.gethostbyaddress ( myip );
hostname = myhost.hostname.tostring ( ) ;
}
catch
{
hostname = "" ;
}
if (hostname == "")
hostname = " 主機沒有響應(yīng)!";
if ( ul != null)
ul ( ip , hostname ) ;
}
//定義一個過程(也可以看出為方法),用以判斷傳送來的ip地址對應(yīng)計算機是否在線
}
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事件對應(yīng)的處理代碼,下列代碼功能是創(chuàng)建多個掃描給定ip地址區(qū)間對應(yīng)的計算機線程實例,并顯示掃描結(jié)果。private void button1_click(object sender, system.eventargs e)
{
listbox1.items.clear ( ) ;
//清楚掃描結(jié)果顯示區(qū)域
starttime = datetime.now ;
//獲取當(dāng)前時間
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地址區(qū)間不合法,請檢查!" , "錯誤!" ) ;
return ;
}
//判斷輸入的ip地址區(qū)間是否合法
int _threadnum = max - min + 1 ;
thread[] mythread = new thread [ _threadnum ] ;
//創(chuàng)建一個多個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 ( ) ;
//創(chuàng)建一個ping實例
hostping.ip = mask + i.tostring ( ) ;
hostping.ul = new updatelist ( updatemylist ) ;
//向這個ping實例中傳遞ip地址字符串
mythread[k] = new thread ( new threadstart ( hostping.scan ) ) ;
//初始化一個線程實例
mythread[k].start ( ) ;
//啟動線程
}
}
至此,【掃描網(wǎng)絡(luò)計算機】項目已經(jīng)被修改成一個多線程的程序了,此時在運行程序,并且同樣再掃描上面給定ip地址區(qū)間對應(yīng)的計算機,就會驚奇的發(fā)現(xiàn)程序執(zhí)行時間所建為10秒了,并且不論要掃描的計算機數(shù)目有多少,程序的運行時間也是10秒左右,這是因為程序為掃描每一個ip都分配一個線程,這樣程序的執(zhí)行時間就不與要掃描的ip地址段中的ip地址數(shù)目有關(guān)聯(lián)了,這樣也就大大減少了程序的運行時間,提高了程序的運行效率,這也充分體現(xiàn)出多線程給網(wǎng)絡(luò)編程帶來的好處。圖03也是程序掃描"10.138.198.1"至"10.138.198.10"這個ip地址區(qū)間計算機后的運行界面所示:
圖03:基于多線程的【掃描網(wǎng)絡(luò)計算機】項目的運行界面
通過對二個程序的比較可見,在編寫網(wǎng)絡(luò)應(yīng)用程序中,正確的使用線程的確能夠大大提高程序的運行效率。
六.總結(jié):
至此,本節(jié)要介紹的內(nèi)容就全部結(jié)束了,不知道諸位通過上面的介紹是否了解、掌握了下面幾點:
1. 如何獲取系統(tǒng)當(dāng)前時間,和實現(xiàn)時間日期類型數(shù)據(jù)的加減。
2. 在編寫網(wǎng)絡(luò)應(yīng)用程序時候,使用線程(多線程)的原因,以及線程(多線程)會給網(wǎng)絡(luò)應(yīng)用程序帶來什么好處。
3. 如何在應(yīng)用程序中創(chuàng)建多個線程實例。
4. 如何實現(xiàn)帶"返回值"的線程。
如果上述要點你都能夠掌握,那是再好不過的了。但如果你對線程及其使用方法還感覺模糊,那也不要緊,畢竟線程在編程技術(shù)中是一個內(nèi)容豐富,使用復(fù)雜的東東,要立馬掌握的確是很困難的事情。在以后的文章中也將再介紹這方面的內(nèi)容。
新聞熱點
疑難解答
圖片精選