C#多線程系列之線程池_第1頁
C#多線程系列之線程池_第2頁
C#多線程系列之線程池_第3頁
C#多線程系列之線程池_第4頁
C#多線程系列之線程池_第5頁
已閱讀5頁,還剩9頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

第C#多線程系列之線程池目錄線程池ThreadPool常用屬性和方法線程池說明和示例線程池線程數(shù)線程池線程數(shù)說明不支持的線程池異步委托任務(wù)取消功能計時器

線程池

線程池全稱為托管線程池,線程池受.NET通用語言運行時(CLR)管理,線程的生命周期由CLR處理,因此我們可以專注于實現(xiàn)任務(wù),而不需要理會線程管理。

線程池的應(yīng)用場景:任務(wù)并行庫(TPL)操作、異步I/O完成、計時器回調(diào)、注冊的等待操作、使用委托的異步方法調(diào)用和套接字連接。

很多人不清楚Task、TaskTResult原理,原因是沒有好好了解線程池。

ThreadPool常用屬性和方法

屬性:

屬性說明CompletedWorkItemCount獲取迄今為止已處理的工作項數(shù)。PendingWorkItemCount獲取當前已加入處理隊列的工作項數(shù)。ThreadCount獲取當前存在的線程池線程數(shù)。

方法:

方法說明BindHandle(IntPtr)將操作系統(tǒng)句柄綁定到ThreadPool。BindHandle(SafeHandle)將操作系統(tǒng)句柄綁定到ThreadPool。GetAvailableThreads(Int32,Int32)檢索由GetMaxThreads(Int32,Int32)方法返回的最大線程池線程數(shù)和當前活動線程數(shù)之間的差值。GetMaxThreads(Int32,Int32)檢索可以同時處于活動狀態(tài)的線程池請求的數(shù)目。所有大于此數(shù)目的請求將保持排隊狀態(tài),直到線程池線程變?yōu)榭捎?。GetMinThreads(Int32,Int32)發(fā)出新的請求時,在切換到管理線程創(chuàng)建和銷毀的算法之前檢索線程池按需創(chuàng)建的線程的最小數(shù)量。QueueUserWorkItem(WaitCallback)將方法排入隊列以便執(zhí)行。此方法在有線程池線程變得可用時執(zhí)行。QueueUserWorkItem(WaitCallback,Object)將方法排入隊列以便執(zhí)行,并指定包含該方法所用數(shù)據(jù)的對象。此方法在有線程池線程變得可用時執(zhí)行。QueueUserWorkItem(Action,TState,Boolean)將Action委托指定的方法排入隊列以便執(zhí)行,并提供該方法使用的數(shù)據(jù)。此方法在有線程池線程變得可用時執(zhí)行。RegisterWaitForSingleObject(WaitHandle,WaitOrTimerCallback,Object,Int32,Boolean)注冊一個等待WaitHandle的委托,并指定一個32位有符號整數(shù)來表示超時值(以毫秒為單位)。SetMaxThreads(Int32,Int32)設(shè)置可以同時處于活動狀態(tài)的線程池的請求數(shù)目。所有大于此數(shù)目的請求將保持排隊狀態(tài),直到線程池線程變?yōu)榭捎谩etMinThreads(Int32,Int32)發(fā)出新的請求時,在切換到管理線程創(chuàng)建和銷毀的算法之前設(shè)置線程池按需創(chuàng)建的線程的最小數(shù)量。UnsafeQueueNativeOverlapped(NativeOverlapped)將重疊的I/O操作排隊以便執(zhí)行。UnsafeQueueUserWorkItem(IThreadPoolWorkItem,Boolean)將指定的工作項對象排隊到線程池。UnsafeQueueUserWorkItem(WaitCallback,Object)將指定的委托排隊到線程池,但不會將調(diào)用堆棧傳播到輔助線程。UnsafeRegisterWaitForSingleObject(WaitHandle,WaitOrTimerCallback,Object,Int32,Boolean)注冊一個等待WaitHandle的委托,并使用一個32位帶符號整數(shù)來表示超時時間(以毫秒為單位)。此方法不將調(diào)用堆棧傳播到輔助線程。

線程池說明和示例

通過System.Threading.ThreadPool類,我們可以使用線程池。

ThreadPool類是靜態(tài)類,它提供一個線程池,該線程池可用于執(zhí)行任務(wù)、發(fā)送工作項、處理異步I/O、代表其他線程等待以及處理計時器。

理論的東西這里不會說太多,你可以參考官方文檔地址:/zh-cn/dotnet/api/system.threading.threadpoolview=netcore-3.1

ThreadPool有一個QueueUserWorkItem()方法,該方法接受一個代表用戶異步操作的委托(名為WaitCallback),調(diào)用此方法傳入委托后,就會進入線程池內(nèi)部隊列中。

WaitCallback委托的定義如下:

publicdelegatevoidWaitCallback(objectstate);

現(xiàn)在我們來寫一個簡單的線程池示例,再扯淡一下。

classProgram

staticvoidMain(string[]args)

ThreadPool.QueueUserWorkItem(MyAction);

ThreadPool.QueueUserWorkItem(state=

Console.WriteLine("任務(wù)已被執(zhí)行2");

Console.ReadKey();

//state表示要傳遞的參數(shù)信息,這里為null

privatestaticvoidMyAction(Objectstate)

Console.WriteLine("任務(wù)已被執(zhí)行1");

}

十分簡單對不對~

這里有幾個要點:

不要將長時間運行的操作放進線程池中;不應(yīng)該阻塞線程池中的線程;線程池中的線程都是后臺線程(又稱工作者線程);

另外,這里一定要記住WaitCallback這個委托。

我們觀察創(chuàng)建線程需要的時間:

staticvoidMain()

Stopwatchwatch=newStopwatch();

watch.Start();

for(inti=0;ii++)

newThread(()={}).Start();

watch.Stop();

Console.WriteLine("創(chuàng)建10個線程需要花費時間(毫秒):"+watch.ElapsedMilliseconds);

Console.ReadKey();

}

筆者電腦測試結(jié)果大約160。

線程池線程數(shù)

線程池中的SetMinThreads()和SetMaxThreads()可以設(shè)置線程池工作的最小和最大線程數(shù)。其定義分別如下:

//設(shè)置線程池最小工作線程數(shù)線程

publicstaticboolSetMinThreads(intworkerThreads,intcompletionPortThreads);

//獲取

publicstaticvoidGetMinThreads(outintworkerThreads,outintcompletionPortThreads);

workerThreads:要由線程池根據(jù)需要創(chuàng)建的新的最小工作程序線程數(shù)。

completionPortThreads:要由線程池根據(jù)需要創(chuàng)建的新的最小空閑異步I/O線程數(shù)。

SetMinThreads()的返回值代表是否設(shè)置成功。

//設(shè)置線程池最大工作線程數(shù)

publicstaticboolSetMaxThreads(intworkerThreads,intcompletionPortThreads);

//獲取

publicstaticvoidGetMaxThreads(outintworkerThreads,outintcompletionPortThreads);

workerThreads:線程池中輔助線程的最大數(shù)目。

completionPortThreads:線程池中異步I/O線程的最大數(shù)目。

SetMaxThreads()的返回值代表是否設(shè)置成功。

這里就不給出示例了,不過我們也看到了上面出現(xiàn)異步I/O線程這個關(guān)鍵詞,下面會學(xué)習(xí)到相關(guān)知識。

線程池線程數(shù)說明

關(guān)于最大最小線程數(shù),這里有一些知識需要說明。在此前,我們來寫一個示例:

classProgram

staticvoidMain(string[]args)

//不斷加入任務(wù)

for(inti=0;ii++)

ThreadPool.QueueUserWorkItem(state=

Thread.Sleep(100);

Console.WriteLine("");

for(inti=0;ii++)

ThreadPool.QueueUserWorkItem(state=

Thread.Sleep(TimeSpan.FromSeconds(1));

Console.WriteLine("");

Console.WriteLine("此計算機處理器數(shù)量:"+Environment.ProcessorCount);

//工作項、任務(wù)代表同一個意思

Console.WriteLine("當前線程池存在線程數(shù):"+ThreadPool.ThreadCount);

Console.WriteLine("當前已處理的工作項數(shù):"+ThreadPool.CompletedWorkItemCount);

Console.WriteLine("當前已加入處理隊列的工作項數(shù):"+ThreadPool.PendingWorkItemCount);

intcount;

intioCount;

ThreadPool.GetMinThreads(outcount,outioCount);

Console.WriteLine($"默認最小輔助線程數(shù):{count},默認最小異步IO線程數(shù):{ioCount}");

ThreadPool.GetMaxThreads(outcount,outioCount);

Console.WriteLine($"默認最大輔助線程數(shù):{count},默認最大異步IO線程數(shù):{ioCount}");

Console.ReadKey();

}

運行后,筆者電腦輸出結(jié)果(我們的運行結(jié)果可能不一樣):

此計算機處理器數(shù)量:8

當前線程池存在線程數(shù):8

當前已處理的工作項數(shù):2

當前已加入處理隊列的工作項數(shù):8

默認最小輔助線程數(shù):8,默認最小異步IO線程數(shù):8

默認最大輔助線程數(shù):32767,默認最大異步IO線程數(shù):1000

我們結(jié)合運行結(jié)果,來了解一些知識點。

線程池最小線程數(shù),默認是當前計算機處理器數(shù)量。另外我們也看到了。當前線程池存在線程數(shù)為8,因為線程池創(chuàng)建后,無論有沒有任務(wù),都有8個線程存活。

如果將線程池最小數(shù)設(shè)置得過大(SetMinThreads()),會導(dǎo)致任務(wù)切換開銷變大,消耗更多得性能資源。

如果設(shè)置得最小值小于處理器數(shù)量,則也可能會影響性能。

Environment.ProcessorCount可以確定當前計算機上有多少個處理器數(shù)量(例如CPU是四核八線程,結(jié)果就是八)。

SetMaxThreads()設(shè)置的最大工作線程數(shù)或I/O線程數(shù),不能小于SetMinThreads()設(shè)置的最小工作線程數(shù)或I/O線程數(shù)。

設(shè)置線程數(shù)過大,會導(dǎo)致任務(wù)切換開銷變大,消耗更多得性能資源。

如果加入的任務(wù)大于設(shè)置的最大線程數(shù),那么將會進入等待隊列。

不能將工作線程或I/O完成線程的最大數(shù)目設(shè)置為小于計算機上的處理器數(shù)。

不支持的線程池異步委托

扯淡了這么久,我們從設(shè)置線程數(shù)中,發(fā)現(xiàn)有個I/O異步線程數(shù),這個線程數(shù)限制的是執(zhí)行異步委托的線程數(shù)量,這正是本節(jié)要介紹的。

異步編程模型(AsynchronousProgrammingModel,簡稱APM),在日常擼碼中,我們可以使用async、await和Task一把梭了事。

.NETCore不再使用BeginInvoke這種模式。你可以跟著筆者一起踩坑先。

筆者在看書的時候,寫了這個示例:

很多地方也在使用這種形式的示例,但是在.NETCore中用不了,只能在.NETFx使用。。。

classProgram

privatedelegatestringMyAsyncDelete(outintthisThreadId);

staticvoidMain(string[]args)

intthreadId;

//不是異步調(diào)用

MyMethodAsync(outthreadId);

//創(chuàng)建自定義的委托

MyAsyncDeletemyAsync=MyMethodAsync;

//初始化異步的委托

IAsyncResultresult=myAsync.BeginInvoke(outthreadId,null,null);

//當前線程等待異步完成任務(wù),也可以去掉

result.AsyncWaitHandle.WaitOne();

Console.WriteLine("異步執(zhí)行");

//檢索異步執(zhí)行結(jié)果

stringreturnValue=myAsync.EndInvoke(outthreadId,result);

//關(guān)閉

result.AsyncWaitHandle.Close();

Console.WriteLine("異步處理結(jié)果:"+returnValue);

privatestaticstringMyMethodAsync(outintthreadId)

//獲取當前線程在托管線程池的唯一標識

threadId=Thread.CurrentThread.ManagedThreadId;

//模擬工作請求

Thread.Sleep(TimeSpan.FromSeconds(newRandom().Next(1,5)));

//返回工作完成結(jié)果

return"喜歡我的讀者可以關(guān)注筆者的博客歐~";

}

目前百度到的很多文章也是.NETFX時代的代碼了,要注意C#在版本迭代中,對異步這些API,做了很多修改,不要看別人的文章,學(xué)完后才發(fā)現(xiàn)不能在.NETCore中使用(例如我......),浪費時間。

上面這個代碼示例,也從側(cè)面說明了,以往.NETFx(C#5.0以前)中使用異步是很麻煩的。

.NETCore是不支持異步委托的,具體可以看/dotnet/runtime/issues/16312

官網(wǎng)文檔明明說支持的/zh-cn/dotnet/api/system.iasyncresultview=netcore-3.1#examples,而且示例也是這樣,搞了這么久,居然不行,我等下一刀過去。

關(guān)于為什么不支持,可以看這里:/dotnet/migrating-delegate-begininvoke-calls-for-net-core/

不支持就算了,我們跳過,后面學(xué)習(xí)異步時再仔細討論。

任務(wù)取消功能

這個取消跟線程池池無關(guān)。

CancellationToken:傳播有關(guān)應(yīng)取消操作的通知。

CancellationTokenSource:向應(yīng)該被取消的CancellationToken發(fā)送信號。

兩者關(guān)系如下:

CancellationTokenSourcects=newCancellationTokenSource();

CancellationTokentoken=cts.Token;

這個取消,在于信號的發(fā)生和信號的捕獲,任務(wù)的取消不是實時的。

示例代碼如下:

CancellationTokenSource實例化一個取消標記,然后傳遞CancellationToken進去;

被啟動的線程,每個階段都判斷.IsCancellationRequested,然后確定是否停止運行。這取決于線程的自覺性。

classProgram

staticvoidMain()

CancellationTokenSourcects=newCancellationTokenSource();

Console.WriteLine("按下回車鍵,將取消任務(wù)");

newThread(()={CanceTask(cts.Token);}).Start();

newThread(()={CanceTask(cts.Token);}).Start();

Console.ReadKey();

//取消執(zhí)行

cts.Cancel();

Console.WriteLine("完成");

Console.ReadKey();

privatestaticvoidCanceTask(CancellationTokentoken)

Console.WriteLine("第一階段");

Thread.Sleep(TimeSpan.FromSeconds(1));

if(token.IsCancellationRequested)

return;

Console.WriteLine("第二階段");

Thread.Sleep(TimeSpan.FromSeconds(1));

if(token.IsCancellationRequested)

return;

Console.WriteLine("第三階段");

Thread.Sleep(TimeSpan.FromSeconds(1));

if(token.IsCancellationRequested)

return;

Console.WriteLine("第四階段");

Thread.Sleep(TimeSpan.FromSeconds(1));

if(token.IsCancellationRequested)

return;

Console.WriteLine("第五階段");

Thread.Sleep(TimeSpan.FromSeconds(1));

if(token.IsCancellationRequested)

return;

}

這個取消標記,在前面的很多同步方式中,都用的上。

計時器

常用的定時器有兩種,分別是:System.Timers.Timer和System.Thread.Timer。

System.Threading.Timer是一個普通的計時器,它是線程池中的線程中。

System.Timers.Timer包裝了System.Threading.Timer,并提供了一些用于在特定線程上分派的其他功能。

什么線程安全不安全。。。俺不懂這個。。。不過你可以參考/questions/19577296/thread-safety-of-system-timers-timer-vs-system-threading-timer

如果你想認真區(qū)分兩者的關(guān)系,可以查看:/web/20150329101415

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論