改善C#程序的建议9:使用Task代替ThreadPool和Thread

简介:

一:Task的优势

ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便。比如:

1: ThreadPool不支持线程的取消、完成、失败通知等交互性操作;

2: ThreadPool不支持线程执行的先后次序;

以往,如果开发者要实现上述功能,需要完成很多额外的工作,现在,FCL中提供了一个功能更强大的概念:Task。Task在线程池的基础上进行了优化,并提供了更多的API。在FCL4.0中,如果我们要编写多线程程序,Task显然已经优于传统的方式。

以下是一个简单的任务示例:

复制代码
static void  Main( string [] args)
{
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方法可以在一个任务完成的时候发起一个新任务,这种方式天然就支持了任务的完成通知:我们可以在新任务中获取原任务的结果值。

       下面是一个稍微复杂一点的例子,同时支持完成通知、取消、获取任务返回值等功能:

复制代码
static void  Main( string [] args)
{
CancellationTokenSource cts 
= new  CancellationTokenSource();
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方法进行处理。如果采用后者的方式,如下:

复制代码
static void  Main( string [] args)
{
CancellationTokenSource cts 
= new  CancellationTokenSource();
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循环,模拟一个异常出来:

复制代码
while  ( true )
{
// ct.ThrowIfCancellationRequested();
if  (result  == 5 )
{
throw new  Exception( " error " );
}
result
++ ;
Thread.Sleep(
1000 );
}
复制代码

模拟异常后的输出为:

任务开始……
任务完成,完成时候的状态为:
IsCanceled
= False IsCompleted = True IsFaulted = True


三:任务工厂

Task还支持任务工厂的概念。任务工厂支持多个任务之间共享相同的状态,如取消类型CancellationTokenSource就是可以被共享的。通过使用任务工厂,可以同时取消一组任务:

复制代码
static void  Main( string [] args)
{
CancellationTokenSource cts 
= new  CancellationTokenSource();
// 等待按下任意一个键取消任务
TaskFactory taskFactory  = new  TaskFactory();
Task[] tasks 
= new  Task[]
{
taskFactory.StartNew(() 
=>  Add(cts.Token)),
taskFactory.StartNew(() 
=>  Add(cts.Token)),
taskFactory.StartNew(() 
=>  Add(cts.Token))
};
// CancellationToken.None指示TasksEnded不能被取消
taskFactory.ContinueWhenAll(tasks, TasksEnded, CancellationToken.None);
Console.ReadKey();
cts.Cancel();
Console.ReadKey();
}

static void  TasksEnded(Task[] tasks)
{
Console.WriteLine(
" 所有任务已完成! " );
}
复制代码

以上代码输出为:

任务开始……
任务开始……
任务开始……
所有任务已完成(取消)!

本建议演示了Task(任务)和TaskFactory(任务工厂)的使用方法。Task甚至进一步优化了后台线程池的调度,加快了线程的处理速度。在FCL4.0时代,使用多线程,我们理应更多地使用Task。


本文转自最课程陆敏技博客园博客,原文链接:http://www.cnblogs.com/luminji/archive/2011/05/13/2044801.html,如需转载请自行联系原作者


相关文章
|
4月前
|
存储 安全 Java
程序与技术分享:C#值类型和引用类型的区别
程序与技术分享:C#值类型和引用类型的区别
34 0
|
20天前
|
C# 容器
C#中的命名空间与程序集管理
在C#编程中,`命名空间`和`程序集`是组织代码的关键概念,有助于提高代码的可维护性和复用性。本文从基础入手,详细解释了命名空间的逻辑组织方式及其基本语法,展示了如何使用`using`指令访问其他命名空间中的类型,并提供了常见问题的解决方案。接着介绍了程序集这一.NET框架的基本单位,包括其创建、引用及高级特性如强名称和延迟加载等。通过具体示例,展示了如何创建和使用自定义程序集,并提出了针对版本不匹配和性能问题的有效策略。理解并善用这些概念,能显著提升开发效率和代码质量。
33 4
|
26天前
|
Linux C# 开发者
Uno Platform 驱动的跨平台应用开发:从零开始的全方位资源指南与定制化学习路径规划,助您轻松上手并精通 C# 与 XAML 编程技巧,打造高效多端一致用户体验的移动与桌面应用程序
【9月更文挑战第8天】Uno Platform 的社区资源与学习路径推荐旨在为初学者和开发者提供全面指南,涵盖官方文档、GitHub 仓库及社区支持,助您掌握使用 C# 和 XAML 创建跨平台原生 UI 的技能。从官网入门教程到进阶技巧,再到活跃社区如 Discord,本指南带领您逐步深入了解 Uno Platform,并提供实用示例代码,帮助您在 Windows、iOS、Android、macOS、Linux 和 WebAssembly 等平台上高效开发。建议先熟悉 C# 和 XAML 基础,然后实践官方教程,研究 GitHub 示例项目,并积极参与社区讨论,不断提升技能。
36 2
|
2月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】C#程序是否有对应的方式来优化并缩短由于 Redis 维护造成的不可访问的时间
【Azure Redis 缓存】C#程序是否有对应的方式来优化并缩短由于 Redis 维护造成的不可访问的时间
|
2月前
|
安全 C# 开发者
【C# 多线程编程陷阱揭秘】:小心!那些让你的程序瞬间崩溃的多线程数据同步异常问题,看完这篇你就能轻松应对!
【8月更文挑战第18天】多线程编程对现代软件开发至关重要,特别是在追求高性能和响应性方面。然而,它也带来了数据同步异常等挑战。本文通过一个简单的计数器示例展示了当多个线程无序地访问共享资源时可能出现的问题,并介绍了如何使用 `lock` 语句来确保线程安全。此外,还提到了其他同步工具如 `Monitor` 和 `Semaphore`,帮助开发者实现更高效的数据同步策略,以达到既保证数据一致性又维持良好性能的目标。
32 0
|
2月前
|
C#
WPF/C#:程序关闭的三种模式
WPF/C#:程序关闭的三种模式
33 0
|
4月前
|
开发框架 .NET 编译器
程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性
程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性
30 2
|
4月前
|
数据采集 XML 存储
技术经验分享:C#构造蜘蛛爬虫程序
技术经验分享:C#构造蜘蛛爬虫程序
28 0
|
4月前
|
安全 编译器 API
程序与技术分享:C#调用DLL的几种方法
程序与技术分享:C#调用DLL的几种方法
54 0