改善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,如需转载请自行联系原作者


相关文章
|
6月前
|
存储 SQL 数据库连接
C#程序调用Sql Server存储过程异常处理:调用存储过程后不返回、不抛异常的解决方案
本文分析了C#程序操作Sql Server数据库时偶发的不返回、不抛异常问题,并提出了解决思路。首先解析了一个执行存储过程的函数`ExecuteProcedure`,其功能是调用存储过程并返回影响行数。针对代码执行被阻塞但无异常的情况,文章总结了可能原因,如死锁、无限循环或网络问题等。随后提供了多种解决方案:1) 增加日志定位问题;2) 使用异步操作提升响应性;3) 设置超时机制避免阻塞;4) 利用线程池分离主线程;5) 通过信号量同步线程;6) 监控数据库连接状态确保可用性。这些方法可有效应对数据库操作中的潜在问题,保障程序稳定性。
517 11
|
缓存 C# Windows
C#程序如何编译成Native代码
【10月更文挑战第15天】在C#中,可以通过.NET Native和第三方工具(如Ngen.exe)将程序编译成Native代码,以提升性能和启动速度。.NET Native适用于UWP应用,而Ngen.exe则通过预编译托管程序集为本地机器代码来加速启动。不过,这些方法也可能增加编译时间和部署复杂度。
709 2
|
11月前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
531 13
|
API C#
C#实现Winform程序右下角弹窗消息提示
C#实现Winform程序右下角弹窗消息提示
702 1
|
C# 容器
C#中的命名空间与程序集管理
在C#编程中,`命名空间`和`程序集`是组织代码的关键概念,有助于提高代码的可维护性和复用性。本文从基础入手,详细解释了命名空间的逻辑组织方式及其基本语法,展示了如何使用`using`指令访问其他命名空间中的类型,并提供了常见问题的解决方案。接着介绍了程序集这一.NET框架的基本单位,包括其创建、引用及高级特性如强名称和延迟加载等。通过具体示例,展示了如何创建和使用自定义程序集,并提出了针对版本不匹配和性能问题的有效策略。理解并善用这些概念,能显著提升开发效率和代码质量。
461 4
|
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 示例项目,并积极参与社区讨论,不断提升技能。
463 2
|
设计模式 程序员 C#
C# 使用 WinForm MDI 模式管理多个子窗体程序的详细步骤
WinForm MDI 模式就像是有超能力一般,让多个子窗体井然有序地排列在一个主窗体之下,既美观又实用。不过,也要小心管理好子窗体们的生命周期哦,否则一不小心就会出现一些意想不到的小bug
1082 0
|
XML 存储 安全
C#开发的程序如何良好的防止反编译被破解?ConfuserEx .NET混淆工具使用介绍
C#开发的程序如何良好的防止反编译被破解?ConfuserEx .NET混淆工具使用介绍
1001 0
|
安全 API C#
C# 如何让程序后台进程不被Windows任务管理器强制结束
C# 如何让程序后台进程不被Windows任务管理器强制结束
512 0