c#Thread、ThreadPool、Task有什么区别,什么时候⽤,以
及Task的使⽤
c# Thread、ThreadPool、Task有什么区别,什么时候⽤,以及Task的使⽤
这三者都是为了处理耗时任务,且都是异步的。
Thread
Thread就是Thread,需要⾃⼰调度,适合长跑型的操作。
ThreadPoll
ThreadPool是Thread基础上的⼀个线程池,⽬的是减少频繁创建线程的开销。线程很贵,要开新的stack,要增加CPU上下⽂切换,所以ThreadPool适合频繁、短期执⾏的⼩操作。调度算法是⾃适应的,会根据程序执⾏的模式调整配置,通常不需要⾃⼰调度线程。另外分为Worker和IO两个池。IO线程对应Native的overlapped io,Win下利⽤IO完成端⼝实现⾮阻塞IO。
【Thread vs. ThreadPoll】
前台线程:主程序必须等待线程执⾏完毕后才可退出程序。Thread默认为前台线程,也可以设置为后台线程。
后台线程:主程序执⾏完毕后就退出,不管线程是否执⾏完毕。ThreadPool默认为后台线程。
线程消耗:开启⼀个新线程,线程不做任何操作,都要消耗1M左右的内存。
总结:ThreadPoll 性能优于 Thread,但是 Thread 和 ThreadPoll 对线程的控制都不是很好,例如线程等待(线程执⾏⼀段时间⽆响应后,直接停⽌线程),释放资源等,都没有直接的API来控制,只能通过硬编码来实现。同时,ThreadPool 使⽤的是线程池全局队列,全局队列中的线程依旧会存在竞争共享资源的情况,从⽽影响性能。
task
Task 是在 .NET Framework 4 中添加进来的。这是新的 namespace:System.Threading.Tasks;它强调的是 adding parallelism and concurrency to applications。在语法上,和 lamda 表达式更好地结合。
Task 背后的实现也是使⽤了线程池线程,但它的性能优于ThreadPoll,因为它使⽤的不是线程池的全局队列,⽽是本地队列,使线程之间的资源竞争减少。同时,Task 提供了丰富的 API 来管理、控制线
程。但是相对前⾯两种耗内存,Task 依赖于 CPU,对于多核的CPU性能远超前两者,⽽对于单核的CPU,三者性能没什么差别。
创建Task有两种⽅法:
//⼯⼚创建,直接执⾏
Task t = Task.Factory.StartNew(() => {
Console.WriteLine("任务已启动....");
});
或者
//直接实例化,必须⼿动去Start
Task t2 = new Task(() => {
Console.WriteLine("开启⼀个新任务");
});
t2.Start();//任务已启动...
第⼀种⽅法不需要调⽤start,初始化后任务就开始了。
Task还可以随时取消正在执⾏的任务,请看下⾯的代码
static void Main(string[] args) {
CancellationTokenSource cts = new CancellationTokenSource();
Task t3 = new Task(() => LongRunTask(cts.Token));
t2.Start();
Thread.Sleep(3000);
cts.Cancel();
Console.Read();
}
private static void LongRunTask(CancellationToken token) {
while (true) {
if (!token.IsCancellationRequested) {
Thread.Sleep(500);
Console.WriteLine(".");
} else {
Console.WriteLine("任务取消了");
break;
}
}
}
Task的任务控制:Task最吸引⼈的地⽅就是他的任务控制了,你可以很好的控制task的执⾏顺序,让多个task有序的⼯作
⽅法名说明
Task.Wait task1.Wait(); 就是等待任务执⾏(task1)完成,task1的状态变为Completed
Task.WaitAll待所有的任务都执⾏完成
Task.WaitAny同Task.WaitAll,就是等待任何⼀个任务完成就继续向下执⾏
Task.ContinueWith第⼀个Task完成后⾃动启动下⼀个Task,实现Task的延续
CancellationTokenSource通过cancellation的tokens来取消⼀个Task
使⽤ Task 代替 ThreadPool 和 Thread
【Task 的优势】
ThreadPool 相⽐Thread来说具备了很多优势,但是ThreadPool却⼜存在⼀些使⽤上的不⽅便。⽐如:
1. ThreadPool 不⽀持线程的取消、完成、失败通知等交互性操作;
2. ThreadPool 不⽀持线程执⾏的先后次序。
以往,如果开发者要实现上述功能,需要完成很多额外的⼯作,现在,FCL中提供了⼀个功能更强⼤的概念:Task。Task在线程池的基础上进⾏了优化,并提供了更多的API。在 FCL4.0 中,如果我们要编写多线程程序,Task显然已经优于传统的⽅式。
以下是⼀个简单的任务⽰例:writeline用什么替代
{
Task t = new Task(() =>
{
Console.WriteLine("任务开始⼯作……");
//模拟⼯作过程
Thread.Sleep(5000);
});
t.Start();
t.ContinueWith((task) =>
{
Console.WriteLine("任务完成,完成时候的状态为:");
Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}",
task.IsCanceled, task.IsCompleted, task.IsFaulted);
});
Console.ReadKey();
}
【Task的完成状态】
任务 Task 有这样⼀些属性,让我们查询任务完成时的状态:
1: IsCanceled,因为被取消⽽完成;
2: IsCompleted,成功完成;
3: IsFaulted,因为发⽣异常⽽完成。
需要注意的是,任务并没有提供回调事件来通知完成(像 BackgroundWorker ⼀样),它通过启⽤⼀个新任务的⽅式来完成类似的功能。ContinueWith ⽅法可以在⼀个任务完成的时候发起⼀个新任务,这种⽅式天然就⽀持了任务的完成通知:可以在新任务中获取原任务的结果值。
下⾯是⼀个稍微复杂⼀点的例⼦,同时⽀持完成通知、取消、获取任务返回值等功能:
Task<int> t = new Task<int>(() => Add(cts.Token), cts.Token);
t.Start();
t.ContinueWith(TaskEnded);
//等待按下任意⼀个键取消任务
Console.ReadKey();
cts.Cancel();
Console.ReadKey();
}
static void TaskEnded(Task<int> task)
{
Console.WriteLine("任务完成,完成时候的状态为:");
Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}",
task.IsCanceled, task.IsCompleted, task.IsFaulted);
Console.WriteLine("任务的返回值为:{0}", task.Result);
}
static int Add(CancellationToken ct)
{
Console.WriteLine("任务开始……");
int result = 0;
while (!ct.IsCancellationRequested)
{
result++;
Thread.Sleep(1000);
}
return result;
}
在任务开始后⼤概3秒钟的时候按下键盘,会得到如下的输出:
任务开始……
任务完成,完成时候的状态为:
IsCanceled=False IsCompleted=True IsFaulted=False
任务的返回值为:3
你也许会奇怪,我们的任务是通过Cancel的⽅式处理,为什么完成的状态IsCanceled那⼀栏还是False。这是因为在⼯作任务中,我们对于IsCancellationRequested进⾏了业务逻辑上的处理,并没有通过ThrowIfCancellationRequested⽅法进⾏处理。如果采⽤后者的⽅式,如下:
Task<int> t =new Task<int>(() => AddCancleByThrow(cts.Token), cts.Token);
t.Start();
t.ContinueWith(TaskEndedByCatch);
//等待按下任意⼀个键取消任务
Console.ReadKey();
cts.Cancel();
Console.ReadKey();
}
static void TaskEndedByCatch(Task<int> task)
{
Console.WriteLine("任务完成,完成时候的状态为:");
Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}",
task.IsCanceled, task.IsCompleted, task.IsFaulted);
try
{
Console.WriteLine("任务的返回值为:{0}", task.Result);
}
catch (AggregateException e)
{
e.Handle((err) => err is OperationCanceledException);
}
}
static int AddCancleByThrow(CancellationToken ct)
{
Console.WriteLine("任务开始……");
int result = 0;
while (true)
{
ct.ThrowIfCancellationRequested();
result++;
Thread.Sleep(1000);
}
return result;
}
那么输出为:
任务开始……
任务完成,完成时候的状态为:
IsCanceled=True IsCompleted=True IsFaulted=False
在任务结束求值的⽅法TaskEndedByCatch中,如果任务是通过 ThrowIfCancellationRequested ⽅法结束的,对任务求结果值将会抛出异常 OperationCanceledException,⽽不是得到抛出异常前的结果值。这意味着任务是通过异常的⽅式被取消掉的,所以可以注意到上⾯代码的输出中,状态 IsCancled 为True。
再⼀次,我们注意到取消是通过异常的⽅式实现的,⽽表⽰任务中发⽣了异常的IsFaulted状态却还是等于False。这是因为ThrowIfCancellationRequested 是协作式取消⽅式类型 CancellationTokenSource 的⼀个⽅法,CLR进⾏了特殊的处理。CLR知道这⼀⾏程序开发者有意为之的代码,所以不把它看作是⼀个异常(它被理解为取消)。要得到 IsFaulted 等于 True 的状态,我们可以修改While 循环,模拟⼀个异常出来:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论