C#基礎(chǔ)–進(jìn)程和線程的認(rèn)識
一、基礎(chǔ)概念
1. 什么是進(jìn)程?
進(jìn)程并不是物理的東西,是虛擬出來的,是一種概念。當(dāng)一個程序開始運(yùn)行時,它就是一個進(jìn)程,進(jìn)程包括運(yùn)行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。而一個進(jìn)程又是由多個線程所組成的。是一種計(jì)算機(jī)概念,是程序在運(yùn)行的時候,記錄當(dāng)前程序?qū)τ?jì)算機(jī)的各種資源的消耗。
如任務(wù)管理器中的應(yīng)用進(jìn)程等
2. 什么是線程?
線程是程序中的一個執(zhí)行流,每個線程都有自己的專有寄存器(棧指針、程序計(jì)數(shù)器等),但代碼區(qū)是共享的,即不同的線程可以執(zhí)行同樣的函數(shù)。也是一種計(jì)算機(jī)概念,線程是進(jìn)程在響應(yīng)操作的時候一個最小的單元,也包括CPU/硬盤/內(nèi)存等。
3. 什么是句柄?
描述程序中的某一個最小單元,是一個long數(shù)字,操作系統(tǒng)通過這個數(shù)組識別應(yīng)用程序。
4. 什么是多線程?
多線程是指程序中包含多個執(zhí)行流,即在一個程序中可以同時運(yùn)行多個不同的線程來執(zhí)行不同的任務(wù),也就是說允許單個程序創(chuàng)建多個并行執(zhí)行的線程來完成各自的任務(wù)。
**多線程的好處: **
可以提高CPU的利用率。在多線程程序中,一個線程必須等待的時候,CPU可以運(yùn)行其它的線程而不是等待,這樣就大大提高了程序的效率。
多線程的不利方面:
線程也是程序,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多;?
多線程需要協(xié)調(diào)和管理,所以需要CPU時間跟蹤線程;?
線程之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題;
線程太多會導(dǎo)致控制太復(fù)雜,最終可能造成很多Bug;
5. 何時使用多線程?
? 多線程程序一般被用來在后臺執(zhí)行耗時的任務(wù)。主線程保持運(yùn)行,并且工作線程做它的后臺工作。對于Windows Forms程序來說,如果主線程試圖執(zhí)行冗長的操作,鍵盤和鼠標(biāo)的操作會變的遲鈍,程序也會失去響應(yīng)。由于這個原因,應(yīng)該在工作線程中運(yùn)行一個耗時任務(wù)時添加一個工作線程,即使在主線程上有一個有好的提示“處理中…”,以防止工作無法繼續(xù)。這就避免了程序出現(xiàn)由操作系統(tǒng)提示的“沒有相應(yīng)”,來誘使用戶強(qiáng)制結(jié)束程序的進(jìn)程而導(dǎo)致錯誤。模式對話框還允許實(shí)現(xiàn)“取消”功能,允許繼續(xù)接收事件,而實(shí)際的任務(wù)已被工作線程完成。BackgroundWorker恰好可以輔助完成這一功能。
? 在沒有用戶界面的程序里,比如說Windows Service, 多線程在當(dāng)一個任務(wù)有潛在的耗時,因?yàn)樗诘却砼_電腦的響應(yīng)(比如一個應(yīng)用服務(wù)器,數(shù)據(jù)庫服務(wù)器,或者一個客戶端)的實(shí)現(xiàn)特別有意義。用工作線程完成任務(wù)意味著主線程可以立即做其它的事情。
? 另一個多線程的用途是在方法中完成一個復(fù)雜的計(jì)算工作。這個方法會在多核的電腦上運(yùn)行的更快,如果工作量被多個線程分開的話(使用Environment.ProcessorCount屬性來偵測處理芯片的數(shù)量)。
? 一個C#程序稱為多線程的可以通過2種方式:明確地創(chuàng)建和運(yùn)行多線程,或者使用.NET framework的暗中使用了多線程的特性——比如BackgroundWorker類, 線程池,threading timer,遠(yuǎn)程服務(wù)器,或Web Services或ASP.NET程序。在后面的情況,人們別無選擇,必須使用多線程;一個單線程的ASP.NET web server不是太酷,即使有這樣的事情;幸運(yùn)的是,應(yīng)用服務(wù)器中多線程是相當(dāng)普遍的;唯一值得關(guān)心的是提供適當(dāng)鎖機(jī)制的靜態(tài)變量問題。
6. 何時不要使用多線程?
? 多線程也同樣會帶來缺點(diǎn),最大的問題是它使程序變的過于復(fù)雜,擁有多線程本身并不復(fù)雜,復(fù)雜是的線程的交互作用,這帶來了無論是否交互是否是有意的,都會帶來較長的開發(fā)周期,以及帶來間歇性和非重復(fù)性的bugs。因此,要么多線程的交互設(shè)計(jì)簡單一些,要么就根本不使用多線程。除非你有強(qiáng)烈的重寫和調(diào)試欲望。
? 當(dāng)用戶頻繁地分配和切換線程時,多線程會帶來增加資源和CPU的開銷。在某些情況下,太多的I/O操作是非常棘手的,當(dāng)只有一個或兩個工作線程要比有眾多的線程在相同時間執(zhí)行任務(wù)塊的多。稍后我們將實(shí)現(xiàn)生產(chǎn)者/耗費(fèi)者 隊(duì)列,它提供了上述功能。
7. 進(jìn)程和線程的關(guān)系?
它們兩是一種包含關(guān)系,線程是屬于某一個進(jìn)程的,如果一個進(jìn)程被銷毀了,那隸屬于該進(jìn)程的線程也就不存在了。
二、Thread – 線程
Thread 來自于 System.Threading 的一個密封類,它是.Net Framework 1.0 時代出現(xiàn)的,在C# 中用來操作線程的一個幫助類庫;
1. 為什么可以多線程?
-
一個CPU有多個核,可以并行計(jì)算
-
例如我們通常叫的“雙核四線程”,這里的線程其實(shí)是模擬核
2. 什么叫CPU的分片,為什么要對CPU分片呢?
某1s 的處理能切分成 1000 份,操作系統(tǒng)調(diào)度去相應(yīng)不同的任務(wù);
從宏觀的角度來說,感覺就有多個任務(wù)在并發(fā)執(zhí)行;
從微觀的角度來說,一個物理CPU不能在某一時刻為一個任務(wù)服務(wù);
3. 同步方法和異步方法的區(qū)別?
同步方法:同步方法調(diào)用在程序繼續(xù)執(zhí)行之前需要等待同步方法執(zhí)行完畢返回結(jié)果;
private void btnSync_Click(object sender, EventArgs e)
{
for (int i = 0; i < 5; i++)
{
string name = string.Format($"btnSync_Click_{i}");
this.DoSomethingLong(name);
}
}
private void DoSomethingLong(string name)
{
long lResult = 0;
for (int i = 0; i < 1_000_000_000; i++)
{
lResult += i;
}
}
異步方法:在被調(diào)用之后立即返回以便程序在被調(diào)用方法完成其任務(wù)的同時執(zhí)行其它操作。
Action<string> action = this.DoSomethingLong;
for (int i = 0; i < 5; i++)
{
action.BeginInvoke("btnAsync_Click", null, null);
}
private void DoSomethingLong(string name)
{
Console.WriteLine($"****************DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
long lResult = 0;
for (int i = 0; i < 1_000_000_000; i++)
{
lResult += i;
}
Console.WriteLine($"****************DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
}
體驗(yàn)對比
-
同步方法卡界面:主線線程(UI線程)忙于計(jì)算,無暇他顧
-
異步方法不卡界面:因?yàn)楫惒椒椒ㄊ切聠右粋€線程去完后計(jì)算,主線程閑置;改善用戶體驗(yàn),winform程序點(diǎn)擊某一個按鈕,不會卡死界面;發(fā)短信,發(fā)郵件可以交給一個子線程去完成
速度對比
-
同步方法執(zhí)行慢:只有一個線程完成計(jì)算
-
異步方法執(zhí)行快:多個線程去完成計(jì)算,資源換性能
執(zhí)行順序?qū)Ρ?/strong>
-
同步方法有序執(zhí)行
-
異步多線程無順序:啟動無序,結(jié)束無序。線程資源是向操作系統(tǒng)申請的,操作系統(tǒng)有自己的調(diào)度策略,所以啟動是隨機(jī)的,結(jié)束也是沒有順序
4. 如果需要用到線程又需要控制順序呢?怎么實(shí)現(xiàn)?
利用回調(diào)
AsyncCallback callback = (ar) =>
ar 是 IAsyncResult 類型數(shù)據(jù)
Func<string, string> func = (ar) =>
ar 是委托中定義的第一個參數(shù),是string類型數(shù)據(jù)
private void btnAsyncAdvanced_Click(object sender, EventArgs e)
{
//回調(diào):把后續(xù)的動作通過回調(diào)參數(shù)傳遞進(jìn)去,子線程完成計(jì)算以后,去嗲用這個回調(diào)委托
//AsyncCallback 委托要用的方法必須是IAsyncResult 參數(shù),該參數(shù)存了回調(diào)方法所需參數(shù)為object對象。
AsyncCallback callback = ar =>
{
Console.WriteLine($"計(jì)算結(jié)束了。。。。{Thread.CurrentThread.ManagedThreadId}");
};
Func<string, string> func = (ar) => {
Console.WriteLine($"線程Id:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(5 * 1000);
return "Hyl";
};
func.BeginInvoke("大白", callback, null);
}
利用 func.EndInvoke
接收Func 委托中的方法執(zhí)行后的返回值
//IASyncResult參數(shù)1-N由自定義委托AddDelegate決定,AddDelegate有N個參數(shù),那么就有N個參數(shù)
IAsyncResult asyncResult = func.BeginInvoke("大白", callback, null);
var result = func.EndInvoke(asyncResult);
Console.WriteLine("執(zhí)行結(jié)果=" + result);
5. 等待任務(wù)完成
5.1 Func.EndInvoke() 等待任務(wù)完成
委托.EndInvoke(asyncResult) 相當(dāng)于一個監(jiān)視器,一直在監(jiān)視異步委托執(zhí)行完成。一旦完成,則獲取到結(jié)果并賦值到result中,與此同時會異步調(diào)用回調(diào)函數(shù)(有回調(diào)的情況下)。
AsyncCallback callback = (ar) =>
{
Console.WriteLine(JsonConvert.SerializeObject(ar));
Console.WriteLine($"計(jì)算結(jié)束了。。。。{Thread.CurrentThread.ManagedThreadId}");
};
IAsyncResult asyncResult = func.BeginInvoke("大白", callback, null);
var result = func.EndInvoke(asyncResult);
Console.WriteLine("獲取執(zhí)行結(jié)果=" + result);
無論委托中的方法執(zhí)行多久,增加了
func.EndInvoke(asyncResult)
的話,其之后的代碼執(zhí)行便形成了阻塞(變成了一種同步形式),需要等委托的方法執(zhí)行結(jié)束,result 接收到返回?cái)?shù)據(jù)才能往下執(zhí)行(按鈕同步執(zhí)行接收)
//此處的asyncResult 和回調(diào)函數(shù)中的 ar 是同一個類,同樣的數(shù)據(jù)
//回調(diào)函數(shù)中使用 object.ReferenceEquals(ar, asyncResult); => 結(jié)果為:true
IAsyncResult asyncResult = func.BeginInvoke("大白", callback, null);
Console.WriteLine("--------------func.BeginInvoke 異步執(zhí)行開始------------------");
var result = func.EndInvoke(asyncResult);
Console.WriteLine("func.EndInvoke 獲取執(zhí)行結(jié)果=" + result);
Console.WriteLine("--------------btnAsyncAdvanced_Click 同步執(zhí)行結(jié)束------------------");
IAsyncResult 數(shù)據(jù)解析:
{
"IsCompleted": true,
"AsyncDelegate": { //委托方法信息
"Method": {
"Name": "<btnAsyncAdvanced_Click>b__4_1",
"AssemblyName": "MyAsyncThread, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
"ClassName": "MyAsyncThread.frmThreads+<>c",
"Signature": "System.String <btnAsyncAdvanced_Click>b__4_1(System.String)",
"Signature2": "System.String <btnAsyncAdvanced_Click>b__4_1(System.String)",
"MemberType": 8,
"GenericArguments": null
},
"Target": {}
},
"AsyncState": null, //func.BeginInvoke("大白", callback, 52032); 如最后一個參數(shù)設(shè)置了值,則AsyncState便是什么值 "AsyncState": 52032
"CompletedSynchronously": false,
"EndInvokeCalled": true, //如果Func委托未使用EndInvoke的時候,此屬性為 false
"AsyncWaitHandle": { //句柄
"Handle": {
"value": 1796
},
"SafeWaitHandle": {
"IsInvalid": false,
"IsClosed": false
}
},
"NextSink": null
}
5.2 IsCompleted() 等待任務(wù)完成
使用等待的過程中,界面會被卡到文章來源:http://www.zghlxwxcb.cn/news/detail-588514.html
while (!asyncResult.IsCompleted) {
Thread.Sleep(1000);
Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff") + " 線程暫未結(jié)束");
}
var re = func.EndInvoke(asyncResult);
Console.WriteLine("func.EndInvoke 獲取執(zhí)行結(jié)果=" + re);
文章來源地址http://www.zghlxwxcb.cn/news/detail-588514.html
5.3 WaitOne() 完成任務(wù)等待
asyncResult.AsyncWaitHandle.WaitOne(); //一直等待任務(wù)完成
asyncResult.AsyncWaitHandle.WaitOne(-1); //一直等待任務(wù)完成,-1無限期等待
asyncResult.AsyncWaitHandle.WaitOne(3000); //最多等待3000毫秒=3秒,如果超時則不等待了
到了這里,關(guān)于C#基礎(chǔ)--進(jìn)程和線程的認(rèn)識的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!