`
omygege
  • 浏览: 1350288 次
文章分类
社区版块
存档分类
最新评论

多线程编程之计算限制型异步操作(续)

 
阅读更多

1. CLR线程池简介

1.1CLR为什么支持线程池

1.2线程池ThreadPool管理线程

2. 线程执行上下文

2.1线程执行上下文简介

2.2一个简单示例

3.线程池常见应用情景示例

3.1 将一个线程添加至线程池中(向线程传递参数)

3.2 协作式取消

4. Task对象

5. Task常见编程情景

5.1创建Task,并启动该Task

5.2获取Task任务的结果

5.3Task任务异常处理

5.4取消Task

5.5启动新Task

6. 定时器Timer

<4>. Task对象

通过上一篇的介绍,我们知道通过ThreadPool的QueueUserWorkItem方法能够很简单的将一个工作线程添加到线程池中,但是这其中的最大的问题是无法得到工作线程的返回值,还好在.net 4.0中提供了System.Threading.Tasks命名空间中提供了Task类和泛型Task<TResult>类型,非泛型的Task类型(无法得到返回值)如下:

publicclassTask:IThreadPoolWorkItem,IAsyncResult,IDisposable
{
//构造函数重载
publicTask(Actionaction);//无参
publicTask(Action<object>action,objectstate);//向action中传递state参数
publicTask(Actionaction,CancellationTokencancellationToken);//支持协作式取消
//使用creationOptions标志创建Task,TaskCreationOptions将在下面介绍
publicTask(Actionaction,TaskCreationOptionscreationOptions);
//其他组合构造函数重载版本...
//get属性:AsyncState,CreationOptions,CurrentId,Exception,Factory等

//该任务完成之后,启动另外的任务方法:
publicTaskContinueWith(Action<Task>continuationAction);
//重载版本

publicvoidStart();//调用Task的Start来调度任务运行
publicvoidStart(TaskSchedulerscheduler);//使用某个调度器

//在Task上调用Wait方法将“等待”指定Task结束
publicvoidWait();//t.Wait(),将等待t结束
publicstaticvoidWaitAll(paramsTask[]tasks);//等待tasks全部结束
publicstaticintWaitAny(paramsTask[]tasks);
//......
}

泛型的Task<TResult>定义除了上面的方法之外,需要特别注意的是:

publicclassTask<TResult>:Task
{
//构造函数
publicTask(Func<TResult>function);

//得到Task运算结果
publicTResultResult{get;internalset;}
}

最后需要特别指出的是TaskCreationOptions类型,该类型定义在System.Threading.Tasks命名空间中:

publicenumTaskCreationOptions
{
None
=0,
PreferFairness
=1,
LongRunning
=2,//需要长时间运行
AttachedToParent=4,//附加到父Task中
}

<5>. Task使用情景

5.1 创建Task,并启动该Task

通过前面对Task类型的分析可以看出,Task的构造函数和ThreadPoll的QueueUserWorkItem函数的参数很类似,下面是一个生成并启动Task的代码段:

ThreadPool.QueueUserWorkItem
Task t = new Task(ComputeBoundOp, 5);
t.Start();

5.2 获取Task任务的结果

Task相对于线程池来说最重要的一个特性是能够得到Task的返回值,下面演示了这一特性:

static void Main(string[] args)
{
Task<Int32> t = new Task<Int32>(
(n => SumInt32((Int32)n)),
5);
// 启动task
t.Start();
// 等待task完成
t.Wait();
// 输出结果
Console.WriteLine(t.Result);
Console.WriteLine("Press any key to continue..");
Console.ReadKey();
}
private static Int32 SumInt32(Int32 m)
{
Int32 sum = 0;
for (int i = 1; i <= m; ++i)
{
sum += i;
}
return sum;
}

5.3 Task任务异常处理

上面的SumInt32函数如果m的值过大的话,那么sum可能出现溢出的情况,于是SumInt32将抛出异常,那么该Exception该如何处理,Task线程池的做法是允许该Task(SumInt32)返回到线程池中,但是在调用t.Result时将抛出System.AggregateException异常。System.AggregateException类型定义如下:

publicclassAggregateException:Exception
{
//构造函数
//通过函数名称Flatten(压平)也能大致了解该函数的功能,英文的解释:IfanyinnerexceptionsarethemselvesinstancesofAggregateException, //thismethodwillrecursivelyflattenallofthem.
publicAggregateExceptionFlatten();
//通过handle函数的predicate函数决定哪些exception是已经处理的,哪些是未处理的。Eachinvocationofthepredicatereturnstrueorfalsetoin //dicatewhethertheExceptionwashandled.
publicvoidHandle(Func<Exception,bool>predicate);
//其他函数

}

那么一个极端的情况是如果一直不去查询Result属性会怎样?CLR在进行垃圾回收该Task对象时,如果存在未处理的异常,CLR将终止该线程。

5.4 取消Task

static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
Task<Int32> t = new Task<Int32>
(
() => SumInt32(cts.Token, 1000)
);
// 开启任务
t.Start();
// 取消任务,这是可能SumInt32已经完成或者是没有完成
cts.Cancel();
// 得到结果时,可能抛出异常
try
{
Console.WriteLine("The Result is {0}", t.Result);
}
catch (System.AggregateException ex)
{
// 如果异常是因为取消而抛出的,调用handle方法表明该异常是
// 无需处理的,如果还存在未处理的异常,CLR将结束该进程
ex.Handle(e => e is OperationCanceledException);
// 如果没有未处理的异常才能打印这句话
Console.WriteLine("Sum is canceled");
}
Console.WriteLine("Press any key to continue..");
Console.ReadKey();
}
private static Int32 SumInt32(CancellationToken token, Int32 m)
{
Int32 sum = 0;
for (int i = 0; i < m; ++i)
{
// 抛出异常表明任务没有完成,这里将抛出OperationCanceledException
token.ThrowIfCancellationRequested();
sum += i;
}
return sum;

}

5.5 启动新Task

上面的代码中为了得到一个Task的返回值的话,那么需要调用Task的Wait方法,这显然降低了性能。一种更好的形式是但Task结束之后,它自动启动另外的Task去处理这个结果。则可以通过Task类的ContinueWith方法实现,该方法定义如下:

public Task ContinueWith(Action<Task<TResult>> continuationAction);

public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction);
public Task ContinueWith(Action<Task<TResult>> continuationAction, CancellationToken cancellationToken);// 将cancellationToken附加到新Task中

public Task ContinueWith(Action<Task<TResult>> continuationAction, TaskContinuationOptions continuationOptions);

// 其他重载定义

在上面的最后一个重载版本中continuationOptions类型为TaskContinuationOptions,定义如下:

publicenumTaskContinuationOptions
{
None
=0,
//....
AttachedToParent=4,//将Task关联到它的父Task中,这将表明除非所有的子任务完成,否则不会认为父任务完成
//下面的标识指出在什么情况下运行ContinueWith任务
NotOnRanToCompletion=65536,
NotOnFaulted
=131072,
OnlyOnCanceled
=196608,
//更为常用的类型
OnlyOnFaulted=327680,
OnlyOnRanToCompletion
=393216,
//其他类型
}

下面是一段示例代码:

Task<Int32> t = new Task<Int32>
(
( n => SumInt32((Int32)n) ),
10000
);
// 开始该task,并不去关心该task何时结束
t.Start();
// 如果Task结束,并且完成了
t.ContinueWith
(
task => Console.WriteLine("Task result is " + task.Result),
TaskContinuationOptions.OnlyOnRanToCompletion
);
Console.WriteLine("Press any key..");

Console.ReadKey();

<6>. 定时器Timer

在System.Threading命名空间中存在Timer类,能够实现让线程池在每隔一段时间调用一个方法,Timer定义如下:

publicsealedclassTimer:MarshalByRefObject,IDisposable
{
publicTimer(TimerCallbackcallback);
//回调方法callback,state向callback传递的参数,dueTime多长时间之后首次调用callback方法,period每次调用callback的时间间隔
publicTimer(TimerCallbackcallback,objectstate,intdueTime,intperiod);
publicTimer(TimerCallbackcallback,objectstate,TimeSpandueTime,TimeSpanperiod);
//其他重载版本

//修改dueTime和period参数
publicboolChange(intdueTime,intperiod);
//其他重载版本
}

一个简单试用示例:

class Program
{
private static Timer s_timer;
static void Main(string[] args)
{
Console.WriteLine("Main thread : starting a timer");
// 立即开始执行该timer,
using(s_timer = new Timer(ComputeBoundOp, 5, 0, Timeout.Infinite))
{
Console.WriteLine("Main thread : do other work ..");
Thread.Sleep(10000); // 10s
}
}
private static Int32 SumInt32(CancellationToken token, Int32 m)
{
Int32 sum = 0;
for (int i = 0; i < m; ++i)
{
// 抛出异常表明任务没有完成,这里将抛出OperationCanceledException
token.ThrowIfCancellationRequested();
sum += i;
}
return sum;
}
private static Int32 SumInt32(Int32 m)
{
Int32 sum = 0;
for (int i = 1; i <= m; ++i)
{
sum += i;
}
return sum;
}
private static void ComputeBoundOp(Object state)
{
Console.WriteLine("In compute bound operation, state = {0}", state);
// 模拟任务,休眠1s时间
Thread.Sleep(1000);
// 两秒之后在调用这个方法
s_timer.Change(2000, Timeout.Infinite);
// 这个方法返回到线程池中,等待下一个工作项
}
}

分享到:
评论

相关推荐

    java中的并发和多线程编程中文版

    此外,本书还提供了有关并发编程的全方位的详细内容,例如限制和同步、死锁和冲突、依赖于状态的操作控制、异步消息传递和控制流、协作交互,以及如何创建基于web的服务和计算型服务。 本书的读者对象是那些希望掌握...

    CLR.via.C#.(中文第3版)(自制详细书签)Part1

    本书作者作者Jeffrey Richter,.NET和Windows编程领域当之无愧的大师和权威,以著述清楚明了,行文流水,言简意赅著称,在国内具有相当高的知名度,他的著作之一《Windows核心编程(第5版)》屡获殊荣,在国内外都...

    CLR.via.C#.(中文第3版)(自制详细书签)

    25.7 使用专用线程执行异步的计算限制操作 25.8 使用线程的理由 25.9 线程调度和优先级 25.10 前台线程和后台线程 25.11 继续学习 第26章 计算限制的异步操作 26.1 CLR线程池基础 26.2 执行简单的计算限制...

    CLR.via.C#.(中文第3版)(自制详细书签)Part3

    本书作者作者Jeffrey Richter,.NET和Windows编程领域当之无愧的大师和权威,以著述清楚明了,行文流水,言简意赅著称,在国内具有相当高的知名度,他的著作之一《Windows核心编程(第5版)》屡获殊荣,在国内外都...

    CLR.via.C#.(中文第3版)(自制详细书签)Part2

    本书作者作者Jeffrey Richter,.NET和Windows编程领域当之无愧的大师和权威,以著述清楚明了,行文流水,言简意赅著称,在国内具有相当高的知名度,他的著作之一《Windows核心编程(第5版)》屡获殊荣,在国内外都...

    java 面试题 总结

    与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。...

    超级有影响力霸气的Java面试题大全文档

    与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。...

    亮剑.NET深入体验与实战精要2

    4.10 进程与多线程的区别 190 4.11 创建多线程应用程序 191 4.12 WinForm开发常见问题 194 4.12.1 如何设置运行时窗体的起始位置 194 4.12.2 如何使一个窗体在屏幕的最顶端 194 4.12.3 实现窗体渐显效果 194 4.12.4 ...

    亮剑.NET深入体验与实战精要3

    4.10 进程与多线程的区别 190 4.11 创建多线程应用程序 191 4.12 WinForm开发常见问题 194 4.12.1 如何设置运行时窗体的起始位置 194 4.12.2 如何使一个窗体在屏幕的最顶端 194 4.12.3 实现窗体渐显效果 194 4.12.4 ...

    C#微软培训资料

    17.4 异步文件操作 .227 17.5 小 结 .234 第十八章 高 级 话 题 .235 18.1 注册表编程 .235 18.2 在 C #代码中调用 C++和 VB 编写的组件 .240 18.3 版 本 控 制 .249 18.4 代 码 优 化 .252 18.5 小 ...

    asp.net知识库

    ADO.NET 2.0 大批量数据操作和多个动态的结果集 ADO.NET 2.0 异步处理 在ASP.NET中使用WINDOWS验证方式连接SQL SERVER数据库 改进ADO.Net数据库访问方式 ASP.NET 2.0 绑定高级技巧 简单实用的DataSet更新数据库的类+...

    VC++常用的共用函数100多页

    ◆如何让一个数字型变量化为字符型变量◆ 19 ◆如何使用“拉动条”,“上下选择”◆ 19 ◆如何使用postmessage来异步触发某事件◆ 21 ◆如何使用Sendmessage来异步触发某事件◆ 22 ◆如何修改父类的相关控件属性◆ ...

    千方百计笔试题大全

    70、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 17 71、启动一个线程是用run()还是start()? 17 72、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 18 73...

    java面试宝典

    70、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 17 71、启动一个线程是用run()还是start()? 17 72、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 18 73...

    VC++常用功能实例

    ◆如何让一个数字型变量化为字符型变量◆ 19 ◆如何使用“拉动条”,“上下选择”◆ 19 ◆如何使用postmessage来异步触发某事件◆ 21 ◆如何使用Sendmessage来异步触发某事件◆ 22 ◆如何修改父类的相关控件属性◆ ...

    java基础题 很全面

    44. 多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 12 45. 线程的基本概念、线程的基本状态以及状态之间的关系 12 46. 在linux下 怎么查看tomcat的进程? 12 47. 简述逻辑操作(&,|,^)与条件操作(&&,||...

    软件工程-理论与实践(许家珆)习题答案

    ● 软件具有“复杂性”,其开发和运行常受到计算机系统的限制。 2. 软件发展有几个阶段?各有何特征? 答: ① 程序设计阶段。  硬件特征:价格贵、存储容量小、运行可靠性差。  软件特征:只有程序、程序设计...

    最新Java面试宝典pdf版

    50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 52、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 33 53、线程的基本概念...

    Java面试笔试资料大全

    50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 52、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 33 53、线程的基本概念...

Global site tag (gtag.js) - Google Analytics