C#多线程系列之线程池_第1页
C#多线程系列之线程池_第2页
C#多线程系列之线程池_第3页
C#多线程系列之线程池_第4页
C#多线程系列之线程池_第5页
已阅读5页,还剩9页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第C#多线程系列之线程池目录线程池ThreadPool常用属性和方法线程池说明和示例线程池线程数线程池线程数说明不支持的线程池异步委托任务取消功能计时器

线程池

线程池全称为托管线程池,线程池受.NET通用语言运行时(CLR)管理,线程的生命周期由CLR处理,因此我们可以专注于实现任务,而不需要理会线程管理。

线程池的应用场景:任务并行库(TPL)操作、异步I/O完成、计时器回调、注册的等待操作、使用委托的异步方法调用和套接字连接。

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

ThreadPool常用属性和方法

属性:

属性说明CompletedWorkItemCount获取迄今为止已处理的工作项数。PendingWorkItemCount获取当前已加入处理队列的工作项数。ThreadCount获取当前存在的线程池线程数。

方法:

方法说明BindHandle(IntPtr)将操作系统句柄绑定到ThreadPool。BindHandle(SafeHandle)将操作系统句柄绑定到ThreadPool。GetAvailableThreads(Int32,Int32)检索由GetMaxThreads(Int32,Int32)方法返回的最大线程池线程数和当前活动线程数之间的差值。GetMaxThreads(Int32,Int32)检索可以同时处于活动状态的线程池请求的数目。所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。GetMinThreads(Int32,Int32)发出新的请求时,在切换到管理线程创建和销毁的算法之前检索线程池按需创建的线程的最小数量。QueueUserWorkItem(WaitCallback)将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。QueueUserWorkItem(WaitCallback,Object)将方法排入队列以便执行,并指定包含该方法所用数据的对象。此方法在有线程池线程变得可用时执行。QueueUserWorkItem(Action,TState,Boolean)将Action委托指定的方法排入队列以便执行,并提供该方法使用的数据。此方法在有线程池线程变得可用时执行。RegisterWaitForSingleObject(WaitHandle,WaitOrTimerCallback,Object,Int32,Boolean)注册一个等待WaitHandle的委托,并指定一个32位有符号整数来表示超时值(以毫秒为单位)。SetMaxThreads(Int32,Int32)设置可以同时处于活动状态的线程池的请求数目。所有大于此数目的请求将保持排队状态,直到线程池线程变为可用。SetMinThreads(Int32,Int32)发出新的请求时,在切换到管理线程创建和销毁的算法之前设置线程池按需创建的线程的最小数量。UnsafeQueueNativeOverlapped(NativeOverlapped)将重叠的I/O操作排队以便执行。UnsafeQueueUserWorkItem(IThreadPoolWorkItem,Boolean)将指定的工作项对象排队到线程池。UnsafeQueueUserWorkItem(WaitCallback,Object)将指定的委托排队到线程池,但不会将调用堆栈传播到辅助线程。UnsafeRegisterWaitForSingleObject(WaitHandle,WaitOrTimerCallback,Object,Int32,Boolean)注册一个等待WaitHandle的委托,并使用一个32位带符号整数来表示超时时间(以毫秒为单位)。此方法不将调用堆栈传播到辅助线程。

线程池说明和示例

通过System.Threading.ThreadPool类,我们可以使用线程池。

ThreadPool类是静态类,它提供一个线程池,该线程池可用于执行任务、发送工作项、处理异步I/O、代表其他线程等待以及处理计时器。

理论的东西这里不会说太多,你可以参考官方文档地址:/zh-cn/dotnet/api/system.threading.threadpoolview=netcore-3.1

ThreadPool有一个QueueUserWorkItem()方法,该方法接受一个代表用户异步操作的委托(名为WaitCallback),调用此方法传入委托后,就会进入线程池内部队列中。

WaitCallback委托的定义如下:

publicdelegatevoidWaitCallback(objectstate);

现在我们来写一个简单的线程池示例,再扯淡一下。

classProgram

staticvoidMain(string[]args)

ThreadPool.QueueUserWorkItem(MyAction);

ThreadPool.QueueUserWorkItem(state=

Console.WriteLine("任务已被执行2");

Console.ReadKey();

//state表示要传递的参数信息,这里为null

privatestaticvoidMyAction(Objectstate)

Console.WriteLine("任务已被执行1");

}

十分简单对不对~

这里有几个要点:

不要将长时间运行的操作放进线程池中;不应该阻塞线程池中的线程;线程池中的线程都是后台线程(又称工作者线程);

另外,这里一定要记住WaitCallback这个委托。

我们观察创建线程需要的时间:

staticvoidMain()

Stopwatchwatch=newStopwatch();

watch.Start();

for(inti=0;ii++)

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

watch.Stop();

Console.WriteLine("创建10个线程需要花费时间(毫秒):"+watch.ElapsedMilliseconds);

Console.ReadKey();

}

笔者电脑测试结果大约160。

线程池线程数

线程池中的SetMinThreads()和SetMaxThreads()可以设置线程池工作的最小和最大线程数。其定义分别如下:

//设置线程池最小工作线程数线程

publicstaticboolSetMinThreads(intworkerThreads,intcompletionPortThreads);

//获取

publicstaticvoidGetMinThreads(outintworkerThreads,outintcompletionPortThreads);

workerThreads:要由线程池根据需要创建的新的最小工作程序线程数。

completionPortThreads:要由线程池根据需要创建的新的最小空闲异步I/O线程数。

SetMinThreads()的返回值代表是否设置成功。

//设置线程池最大工作线程数

publicstaticboolSetMaxThreads(intworkerThreads,intcompletionPortThreads);

//获取

publicstaticvoidGetMaxThreads(outintworkerThreads,outintcompletionPortThreads);

workerThreads:线程池中辅助线程的最大数目。

completionPortThreads:线程池中异步I/O线程的最大数目。

SetMaxThreads()的返回值代表是否设置成功。

这里就不给出示例了,不过我们也看到了上面出现异步I/O线程这个关键词,下面会学习到相关知识。

线程池线程数说明

关于最大最小线程数,这里有一些知识需要说明。在此前,我们来写一个示例:

classProgram

staticvoidMain(string[]args)

//不断加入任务

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("此计算机处理器数量:"+Environment.ProcessorCount);

//工作项、任务代表同一个意思

Console.WriteLine("当前线程池存在线程数:"+ThreadPool.ThreadCount);

Console.WriteLine("当前已处理的工作项数:"+ThreadPool.CompletedWorkItemCount);

Console.WriteLine("当前已加入处理队列的工作项数:"+ThreadPool.PendingWorkItemCount);

intcount;

intioCount;

ThreadPool.GetMinThreads(outcount,outioCount);

Console.WriteLine($"默认最小辅助线程数:{count},默认最小异步IO线程数:{ioCount}");

ThreadPool.GetMaxThreads(outcount,outioCount);

Console.WriteLine($"默认最大辅助线程数:{count},默认最大异步IO线程数:{ioCount}");

Console.ReadKey();

}

运行后,笔者电脑输出结果(我们的运行结果可能不一样):

此计算机处理器数量:8

当前线程池存在线程数:8

当前已处理的工作项数:2

当前已加入处理队列的工作项数:8

默认最小辅助线程数:8,默认最小异步IO线程数:8

默认最大辅助线程数:32767,默认最大异步IO线程数:1000

我们结合运行结果,来了解一些知识点。

线程池最小线程数,默认是当前计算机处理器数量。另外我们也看到了。当前线程池存在线程数为8,因为线程池创建后,无论有没有任务,都有8个线程存活。

如果将线程池最小数设置得过大(SetMinThreads()),会导致任务切换开销变大,消耗更多得性能资源。

如果设置得最小值小于处理器数量,则也可能会影响性能。

Environment.ProcessorCount可以确定当前计算机上有多少个处理器数量(例如CPU是四核八线程,结果就是八)。

SetMaxThreads()设置的最大工作线程数或I/O线程数,不能小于SetMinThreads()设置的最小工作线程数或I/O线程数。

设置线程数过大,会导致任务切换开销变大,消耗更多得性能资源。

如果加入的任务大于设置的最大线程数,那么将会进入等待队列。

不能将工作线程或I/O完成线程的最大数目设置为小于计算机上的处理器数。

不支持的线程池异步委托

扯淡了这么久,我们从设置线程数中,发现有个I/O异步线程数,这个线程数限制的是执行异步委托的线程数量,这正是本节要介绍的。

异步编程模型(AsynchronousProgrammingModel,简称APM),在日常撸码中,我们可以使用async、await和Task一把梭了事。

.NETCore不再使用BeginInvoke这种模式。你可以跟着笔者一起踩坑先。

笔者在看书的时候,写了这个示例:

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

classProgram

privatedelegatestringMyAsyncDelete(outintthisThreadId);

staticvoidMain(string[]args)

intthreadId;

//不是异步调用

MyMethodAsync(outthreadId);

//创建自定义的委托

MyAsyncDeletemyAsync=MyMethodAsync;

//初始化异步的委托

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

//当前线程等待异步完成任务,也可以去掉

result.AsyncWaitHandle.WaitOne();

Console.WriteLine("异步执行");

//检索异步执行结果

stringreturnValue=myAsync.EndInvoke(outthreadId,result);

//关闭

result.AsyncWaitHandle.Close();

Console.WriteLine("异步处理结果:"+returnValue);

privatestaticstringMyMethodAsync(outintthreadId)

//获取当前线程在托管线程池的唯一标识

threadId=Thread.CurrentThread.ManagedThreadId;

//模拟工作请求

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

//返回工作完成结果

return"喜欢我的读者可以关注笔者的博客欧~";

}

目前百度到的很多文章也是.NETFX时代的代码了,要注意C#在版本迭代中,对异步这些API,做了很多修改,不要看别人的文章,学完后才发现不能在.NETCore中使用(例如我......),浪费时间。

上面这个代码示例,也从侧面说明了,以往.NETFx(C#5.0以前)中使用异步是很麻烦的。

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

官网文档明明说支持的/zh-cn/dotnet/api/system.iasyncresultview=netcore-3.1#examples,而且示例也是这样,搞了这么久,居然不行,我等下一刀过去。

关于为什么不支持,可以看这里:/dotnet/migrating-delegate-begininvoke-calls-for-net-core/

不支持就算了,我们跳过,后面学习异步时再仔细讨论。

任务取消功能

这个取消跟线程池池无关。

CancellationToken:传播有关应取消操作的通知。

CancellationTokenSource:向应该被取消的CancellationToken发送信号。

两者关系如下:

CancellationTokenSourcects=newCancellationTokenSource();

CancellationTokentoken=cts.Token;

这个取消,在于信号的发生和信号的捕获,任务的取消不是实时的。

示例代码如下:

CancellationTokenSource实例化一个取消标记,然后传递CancellationToken进去;

被启动的线程,每个阶段都判断.IsCancellationRequested,然后确定是否停止运行。这取决于线程的自觉性。

classProgram

staticvoidMain()

CancellationTokenSourcects=newCancellationTokenSource();

Console.WriteLine("按下回车键,将取消任务");

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

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

Console.ReadKey();

//取消执行

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

如果你想认真区分两者的关系,可以查看:/web/20150329101415

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论