博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

使用Task Library 笔记一

Posted on 2012-02-27 16:26  蔡豹  阅读(312)  评论(0)    收藏  举报

使用Task Library非常简单,就像手动建立Thread一般直觉,它拥有Thread Pool所有的有点,没有Thread Pool的缺点,下面来看一个最简单的Task代码

 1   static void Main(string[] args)
2 {
3 int num = 100;
4 Task t = new Task(() => {
5 num = num * 2;
6 });
7 t.Start();
8 t.Wait();//等等Task完成执行过程
9 Console.WriteLine(num);
10 }

Task代表着一个线程(Thread),当调用Start方法时,内建的排程器会查找看有没有空闲的Thread,如果存在就会将此Task交给它执行,否则就看Thread Pool是否达到上限,没有的 话建立新的Thread来执行,如果Thread Pool达到上限就排队等待空的Thread来执行,下面一段代码是用ThreadPool.QueueUserWorkItem来做,达到同样的目的

1        int num = 100;
2 ManualResetEvent mr = new ManualResetEvent(false);
3 ThreadPool.QueueUserWorkItem((state) =>
4 {
5 num = num * 2;
6 mr.Set();
7 });
8 mr.WaitOne();
9 Console.WriteLine(num);

很明显,使用QueueUserWorkItem来达到上面同样的目的,要比Task来的麻烦,因为要建立Wait Handle来等待Thread Pool结束。

下面是另外一个Task<TResult>类别,还能让Thread程式更加简单:

1        Task<int> t = new Task<int>(() => { 
2 int num=100;
3 return num * 2;
4 });
5 t.Start();
6 Console.WriteLine(t.Result);

Task<TResult>是用来定义一个具有返回值的异步操作。现在再来看上面的代码,发现了吗?原本放在外面的num现在已经放到里面了,这样取值时就不用拿变量的值,而直接用Task.Result来取,Task.Wait也不需要了,因为在调用Task.Result时会自动调用Task.Wait。

还有一种更清楚的写法,就是用ContinueWith:

 1   Task<int> t = new Task<int>(() =>
2 {
3 int num = 100;
4 return num * 2;
5 });
6 Task t2= t.ContinueWith((Task<int> task) => {
7 Console.WriteLine(task.Result);
8 });
9 t2.ContinueWith((Task task) => {
10 Console.WriteLine("Done!");
11 });
12 t.Start();
13 Console.ReadLine();

调用ContinueWith所传入的委托会在Task结束时触发,形成了Threads的执行串链

与Thread的独立性不同,Task有父子关系,在Task的委托中在建立Task时,可以传入一个TaskCreateingOptions来告诉Task Library,此Task必须是外围Task的子Task,这样一来,在调用父Task的Wait时,便会等待子Task执行结束才返回,如下:

 1   Task<int> t = new Task<int>(() => {
2 //子Task
3 Task<int> t2 = new Task<int>(() =>
4 {
5 int num = 100;
6 return num * 2;
7 }, TaskCreationOptions.AttachedToParent);
8 Task<int> t3 = new Task<int>(() =>
9 {
10 int num = 100;
11 return num * 2;
12 }, TaskCreationOptions.AttachedToParent);
13 t2.Start();
14 t3.Start();
15 return t2.Result + t3.Result;
16 });
17 t.Start();
18 Console.WriteLine(t.Result);

TaskCreateingOptions的可能的值:

說明
None
預設值,此Task會被排入Local Queue中等待執行,採LIFO模式。
AttachedToParent
建立的Task必須是外圍的Task之子Task,一樣是放入Local Queue,採LIFO模式。
LongRunning
建立的Task不受Thread Pool所管理,直接新增一個Thread來執行此Task,無等待、無排程。
PreferFairness
建立的Task直接放入Global Queue中,採FIFO模式。

None以及AttachedToParent在上面都用过,此次不需解释,有趣的是LongRunning和PreferFairness.使用LongRunning很简单,使用它所建立的Task就像直接建立Thread一样,其将立刻以Thread Pool之外的Thread执行,不需受限于Thread Pool,代表着Task Library不仅加强了ThreadPool的处理,也延伸了传统手动建立Thread的想法,令其也能获得Task Library如ContinueWith、Wait、Result等。

PreferFairness是比较有趣的参数,以此建立的Task会被强迫放到Global Queue中。(Local Queue为空的清空下Task Library才会尝试由Global Queue取出待处理的Task,这多半用于优先级较低的task),如下是一个可以轻易分辨放在Local Queue跟放在Global Queue不同之处的示例:

 1  List<string> bags = new List<string>();
2 for (int i = 0; i < 10; i++)
3 {
4 Task t = new Task(() =>
5 {
6 Task t2 = new Task(() =>
7 {
8 lock(bags)
9 bags.Add("Inner Thread "+Thread.CurrentThread.ManagedThreadId.ToString());
10 Thread.Sleep(1000);
11 },TaskCreationOptions.PreferFairness);
12 t2.Start();
13 lock(bags)
14 bags.Add("Outer Thread "+Thread.CurrentThread.ManagedThreadId.ToString());
15 Thread.Sleep(1000);
16 });
17 t.Start();
18 }
19
20 Thread.Sleep(15000);
21
22 foreach (var item in bags)
23 Console.WriteLine(item);
24
25 Console.ReadLine();

输出结果:

可以发现,设定为PreferFairness后,Inner Thread被排到后面才执行,当去掉PreferFairness后,结果截然不同。

将上面11行的加粗部分去掉结果如下:

去掉后代码:

......
Task t2 = new Task(() =>
{
lock(bags)
bags.Add("Inner Thread "+Thread.CurrentThread.ManagedThreadId.ToString());
Thread.Sleep(1000);
});
......

输出结果:

Inner Thread在没有PreferFairness的情况下会存入Local Queue中,所有会很快被取出来,如果加上PreferFairness,那么在Local Queue中没有东西的情况下,后面的Task会执行,所有Inner Thread会被排在Outer Thread后面。