多線程荸型,項(xiàng)目中的應(yīng)用不可缺少,能極大的提高程序的響應(yīng)速度乌询,但是也會(huì)提高內(nèi)存和CPU的計(jì)算量(空間換時(shí)間)背蟆,下面簡單介紹下多線程從.NET 1.0版本到.NET 4.0版本的發(fā)展歷程及使用示例
示例工程下載Unity 2017.3.0 P4 .NET版本4.6
在介紹之前先為大家科普下多線程的基礎(chǔ)知識(shí)
- 什么是進(jìn)程
- 什么是線程
- 什么是多線程
- 多線程的優(yōu)點(diǎn)
- 多線程的缺點(diǎn)
- 何時(shí)使用多線程
- 何時(shí)不要使用多線程
- 同步和異步的區(qū)別
什么是進(jìn)程?
進(jìn)程是一個(gè)程序在電腦上運(yùn)行時(shí)劣砍,全部計(jì)算資源的合集叫進(jìn)程惧蛹。當(dāng)一個(gè)程序(例如QQ)開始運(yùn)行時(shí),它就是一個(gè)進(jìn)程,進(jìn)程包括運(yùn)行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源香嗓。而一個(gè)進(jìn)程又是由多個(gè)線程所組成的迅腔。(QQ信息的接受和發(fā)送,圖片接受發(fā)送靠娱,UI界面等)
什么是線程沧烈?
線程是程序的最小執(zhí)行單位,包含計(jì)算資源,,任何一個(gè)操作的響應(yīng)都是線程完成的像云。線程是程序中的一個(gè)執(zhí)行流锌雀,每個(gè)線程都有自己的專有寄存器(棧指針、程序計(jì)數(shù)器等)迅诬,但代碼區(qū)是共享的腋逆,即不同的線程可以執(zhí)行同樣的函數(shù)。(例如QQ的UI界面就是一個(gè)線程處理百框,文字的收發(fā)一個(gè)線程處理闲礼,圖片的收發(fā)一個(gè)線程處理)
什么是多線程?
多線程是指程序中包含多個(gè)執(zhí)行流铐维,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程來執(zhí)行不同的任務(wù)柬泽,也就是說允許單個(gè)程序創(chuàng)建多個(gè)并行執(zhí)行的線程來完成各自的任務(wù)。
多線程的好處:
可以提高CPU的利用率嫁蛇。在多線程程序中锨并,一個(gè)線程必須等待的時(shí)候,CPU可以運(yùn)行其它的線程而不是等待睬棚,這樣就大大提高了程序的效率第煮。多線程特點(diǎn):不卡主線程、速度快抑党、無序性
多線程的不利方面
- 線程也是程序包警,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多底靠;
- 多線程需要協(xié)調(diào)和管理害晦,所以需要CPU時(shí)間跟蹤線程;
- 線程之間對共享資源的訪問會(huì)相互影響暑中,必須解決競用共享資源的問題(線程安全)壹瘟;
- 線程太多會(huì)導(dǎo)致控制太復(fù)雜,最終可能造成很多Bug鳄逾;
何時(shí)使用多線程
多線程程序一般被用來在后臺(tái)執(zhí)行耗時(shí)的任務(wù)稻轨。主線程保持運(yùn)行,并且工作線程做它的后臺(tái)工作雕凹。對于Windows Forms程序來說殴俱,如果主線程試圖執(zhí)行冗長的操作政冻,鍵盤和鼠標(biāo)的操作會(huì)變的遲鈍,程序也會(huì)失去響應(yīng)粱挡。由于這個(gè)原因赠幕,應(yīng)該在工作線程中運(yùn)行一個(gè)耗時(shí)任務(wù)時(shí)添加一個(gè)工作線程,即使在主線程上有一個(gè)有好的提示“處理中...”询筏,以防止工作無法繼續(xù)榕堰。這就避免了程序出現(xiàn)由操作系統(tǒng)提示的“沒有相應(yīng)”,來誘使用戶強(qiáng)制結(jié)束程序的進(jìn)程而導(dǎo)致錯(cuò)誤嫌套。模式對話框還允許實(shí)現(xiàn)“取消”功能逆屡,允許繼續(xù)接收事件,而實(shí)際的任務(wù)已被工作線程完成踱讨。BackgroundWorker恰好可以輔助完成這一功能魏蔗。
在沒有用戶界面的程序里,比如說Windows Service痹筛, 多線程在當(dāng)一個(gè)任務(wù)有潛在的耗時(shí)莺治,因?yàn)樗诘却砼_(tái)電腦的響應(yīng)(比如一個(gè)應(yīng)用服務(wù)器,數(shù)據(jù)庫服務(wù)器帚稠,或者一個(gè)客戶端)的實(shí)現(xiàn)特別有意義谣旁。用工作線程完成任務(wù)意味著主線程可以立即做其它的事情。
另一個(gè)多線程的用途是在方法中完成一個(gè)復(fù)雜的計(jì)算工作滋早。這個(gè)方法會(huì)在多核的電腦上運(yùn)行的更快榄审,如果工作量被多個(gè)線程分開的話(使用Environment.ProcessorCount屬性來偵測處理芯片的數(shù)量)。
一個(gè)C#程序稱為多線程的可以通過2種方式:明確地創(chuàng)建和運(yùn)行多線程杆麸,或者使用.NET framework的暗中使用了多線程的特性——比如BackgroundWorker類, 線程池搁进,threading timer,遠(yuǎn)程服務(wù)器昔头,或Web Services或ASP.NET程序饼问。在后面的情況,人們別無選擇揭斧,必須使用多線程匆瓜;一個(gè)單線程的ASP.NET web server不是太酷,即使有這樣的事情未蝌;幸運(yùn)的是,應(yīng)用服務(wù)器中多線程是相當(dāng)普遍的茧妒;唯一值得關(guān)心的是提供適當(dāng)鎖機(jī)制的靜態(tài)變量問題萧吠。
何時(shí)不要使用多線程
多線程也同樣會(huì)帶來缺點(diǎn),最大的問題是它使程序變的過于復(fù)雜桐筏,擁有多線程本身并不復(fù)雜纸型,復(fù)雜是的線程的交互作用,這帶來了無論是否交互是否是有意的,都會(huì)帶來較長的開發(fā)周期还惠,以及帶來間歇性和非重復(fù)性的bugs泞莉。因此绊困,要么多線程的交互設(shè)計(jì)簡單一些,要么就根本不使用多線程瑰枫。除非你有強(qiáng)烈的重寫和調(diào)試欲望。
當(dāng)用戶頻繁地分配和切換線程時(shí)丹莲,多線程會(huì)帶來增加資源和CPU的開銷光坝。在某些情況下,太多的I/O操作是非常棘手的甥材,當(dāng)只有一個(gè)或兩個(gè)工作線程要比有眾多的線程在相同時(shí)間執(zhí)行任務(wù)快的多盯另。稍后我們將實(shí)現(xiàn)生產(chǎn)者/耗費(fèi)者 隊(duì)列,它提供了上述功能洲赵。
同步和異步的區(qū)別
同步方法調(diào)用在程序繼續(xù)執(zhí)行之前需要等待同步方法執(zhí)行完畢返回結(jié)果(同一時(shí)間只能做一件事)
異步方法則在被調(diào)用之后立即返回以便程序在被調(diào)用方法完成其任務(wù)的同時(shí)執(zhí)行其它操作(同一時(shí)間內(nèi)可以并行做多件事)
話不多說鸳惯,上Code,簡單的異步多線程
/// <summary>
/// 異步方法
/// </summary>
private void AsyncOnClick()
{
Debug.Log($"AsyncOnClick - Start 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Action<string> act = this.DoSomethingLong; //act("同步菜鳥海瀾");//寫法等同于act.Invoke("菜鳥海瀾");
act.BeginInvoke("異步菜鳥海瀾", null, null);
Debug.Log($"AsyncOnClick - End 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
/// <summary>
/// 同步方法
/// </summary>
private void SyncOnClick()
{
Debug.Log($"SyncOnClick - Start 線程ID: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 時(shí)間點(diǎn): {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
this.DoSomethingLong("同步菜鳥海瀾");
Debug.Log($"SyncOnClick - End 線程ID: {Thread.CurrentThread.ManagedThreadId.ToString("00")} 時(shí)間點(diǎn): {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
打印結(jié)果如下
調(diào)用順序是先調(diào)用的異步然后是同步叠萍,但是打印結(jié)果是先出現(xiàn)的同步結(jié)果芝发,說明異步?jīng)]有卡主線程
異步多線程是無序的:啟動(dòng)無序 執(zhí)行時(shí)間不確定 結(jié)束無序,但是要控制他的執(zhí)行順序一般分為三類 回調(diào) 等待 狀態(tài)
回調(diào)
/// <summary>
/// 異步回調(diào)
/// </summary>
private void AsyncCallback()
{
#region 無返回值
{
Action<string> act = this.DoSomethingLong;
IAsyncResult iAsyncResult = null;
AsyncCallback callback = ar =>
{
Debug.Log(object.ReferenceEquals(ar, iAsyncResult));
Debug.Log(ar.AsyncState);
Debug.Log($"這里是BeginInvoke的回調(diào){Thread.CurrentThread.ManagedThreadId.ToString("00")}");
};
iAsyncResult = act.BeginInvoke("AsyncOnClick01", callback, "菜鳥海瀾");
}
#endregion
#region 有返回值
{
Func<int, string> func = i => i.ToString();
//AsyncCallback
IAsyncResult iAsyncResult = func.BeginInvoke(DateTime.Now.Year, ar =>
{
string resultIn = func.EndInvoke(ar);//對于每個(gè)異步操作俭令,只能調(diào)用一次 EndInvoke后德。調(diào)用EndInvoke后立即強(qiáng)制釋放線程,否則系統(tǒng)在合適時(shí)機(jī)釋放線程
Debug.Log($"This is {ar.AsyncState} 的異步調(diào)用結(jié)果 {resultIn}");
}, "回調(diào)傳參菜鳥海瀾");
//string result = func.EndInvoke(iAsyncResult);//獲取返回值 不在回調(diào)中調(diào)用EndInvoke卡主線程
}
#endregion
}
打印結(jié)果如下
等待
/// <summary>
/// 異步等待
/// </summary>
private void AsyncWaitOnClick()
{
{
Action<string> act = this.DoSomethingLong;
IAsyncResult iAsyncResult = act.BeginInvoke("btnAsync_Click", null, null);
int i = 1;
while (!iAsyncResult.IsCompleted)//1 卡界面抄腔,主線程在等待 2 邊等待邊做事兒 3有誤差
{
if (i < 10)
{
Debug.Log($"文件上傳{i++ * 10}%瓢湃。。赫蛇。請等待");
}
else
{
Debug.Log("已完成99%绵患。。悟耘。馬上結(jié)束");
}
Thread.Sleep(200);
}
Debug.Log("文件上傳成功B潋!暂幼!");
act.EndInvoke(iAsyncResult);//等待
Debug.Log("這里是BeginInvoke調(diào)用完成之后才執(zhí)行的筏勒。。旺嬉。");
}
}
打印結(jié)果如下
狀態(tài)
/// <summary>
/// 異步狀態(tài)
/// </summary>
private void AsyncStateOnClick()
{
Debug.Log($"AsyncStateOnClick Start {name} 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 時(shí)間點(diǎn): {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
Action<string> act = this.DoSomethingLong;
IAsyncResult iAsyncResult = act.BeginInvoke("btnAsync_Click", null, null);
//iAsyncResult.AsyncWaitHandle.WaitOne();//一直等待任務(wù)完成管行,第一時(shí)間進(jìn)入下一行
//iAsyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待任務(wù)完成,第一時(shí)間進(jìn)入下一行
iAsyncResult.AsyncWaitHandle.WaitOne(1000);//最多等待1000ms邪媳,否則就進(jìn)入下一行捐顷,可以做一些超時(shí)控制
Debug.Log($"AsyncStateOnClick End {name} 線程ID:{Thread.CurrentThread.ManagedThreadId.ToString("00")} 時(shí)間點(diǎn): {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
}
打印結(jié)果如下