随着 .NET 4.0的到来,她与以前各版本的一个明显差别就是并行功能的增强,以此来适应这个多核的世界。于是引入了一个新概念---任务,作为支持并行运算的重要组成部分,同时,也作为对线程池的一个补充和完善。从所周知,使用线程池有两个明显的缺点,那就是一旦把我们要执行的任务放进去后,什么时候执行完成,以及执行完成后需要返回值,我们都无法通过内置的方式而得知。由于任务(Task)的推出,使得我们对并行编程变得简单,而且不用关心底层是怎么实现的,由于比线程池更灵活,如果能掌握好Task,对于写出高效的并行代码非常有帮助。

一、新建任务

      在System.Threading.Tasks命名空间下,有两个新类,Task及其泛型版本Task<TResult>,这两个类是用来创建任务的,如果执行的代码不需要返回值,请使用Task,若需要返回值,请使用Task<TResult>。

创建任务的方式有两种,一种是通过Task.Factory.StartNew方法来创建一个新任务,如:

Task task = Task.Facotry.StartNew(()=>Console.WriteLine(“Hello, World!”));//此行代码执行后,任务就开始执行

另一种方法是通过Task类的构造函数来创建一个新任务,如:

Task task = new Task(()=>Console.WriteLine(“Hello, World!”));//此处只把要完成的工作交给任务,但任务并未开始

task.Start();//调用Start方法后,任务才会在将来某个时候开始执行。

同时,我们可以调用Wait方法来等待任务的完成或者调用IsCompleted属性来判断任务是否完成。需要说明的是,两种创建任务的方法都可以配合TaskCreationOptions枚举来实现我们对任务执行的行为具体控制, 同时,这两种创建方式允许我们传递一个TaskCreationOptions对象来取消正在运行中的任务,请看任务的取消。

二、任务的取消

     这世界唯一不变的就是变化,当外部条件发生变化时,我们可能会取消正在执行的任务。对于.NET 4.0之前,.NET并未提供一个内置的解决方案来取消线程池中正在执行的代码,但在.NET 4.0中,我们有了Cooperative Cancellation模式,这使得取消正在执行的任务变得非常简单。如下所示:

using System; 
using System.Threading; 
using System.Threading.Tasks;

namespace TaskDemo 

    class Program 
    { 
        static void Main() 
        { 
            CancellationTokenSource cts = new CancellationTokenSource(); 
            Task t = new Task(() => LongRunTask(cts.Token)); 
            t.Start(); 
            Thread.Sleep(2000); 
            cts.Cancel(); 
            Console.Read(); 
        }

static void LongRunTask(CancellationToken token) 
        {

//此处方法模拟一个耗时的工作 
            for (int i = 0; i < 1000; i++) 
            { 
                if (!token.IsCancellationRequested) 
                { 
                    Thread.Sleep(500); 
                    Console.Write("."); 
                } 
                else 
                { 
                    Console.WriteLine("任务取消"); 
                    break; 
                } 
            } 
        } 
    } 
}

三、任务的异常机制

在任务执行过程中产生的未处理异常,任务会把它暂时隐藏起来,装进一个集合中。当我们调用Wait方法或者Result属性时,任务会抛出一个AggregateException异常。我们可以通过调用AggregateException对象的只读属性InnerExceptions来得到一个ReadOnlyCollection<Exception>对象,它才是存储抛出异常的集合,它的第一个元素就是最初抛出的异常。同样的,AggregateException对象的InnerException属性也会返回最初抛出的异常。

值得重视的是,由于任务的隐藏机制的特点,一旦产生异常后,如果我们不调用相应的方法或者属性查看异常,我们也无法判断是否有异常产生(Task不会主动抛出异常)。当Task对象被GC回收时,Finalize方法会查检是否有未处理的异常,如果不幸刚才好有,则Finalize方法会将此AggregateException再度抛出,如果再不幸,我们没有捕获处理这个异常,则我们的程序会立即中止运行。如果发生这样的事情,会是多么大的灾难啊!

为了避免这种不幸的发生,我们可以通过注册TaskScheduler类的静态UnobservedTaskException事件来处理这种未被处理的异常,避免程序的崩溃。

四、任务启动任务

    任务的强大与灵活之一是,当我们完成一个任务时,可以自动开始一个新任务的执行。如下所示:

using System; 
using System.Threading; 
using System.Threading.Tasks;

namespace TaskDemo 

    public class AutoTask 
    { 
        static void Main() 
        { 
            Task task = new Task(() => { Thread.Sleep(5000); Console.WriteLine("Hello,"); Thread.Sleep(5000); }); 
            task.Start(); 
            Task newTask = task.ContinueWith(t => Console.WriteLine("World!")); 
            Console.Read(); 
        } 
    } 
}

对于ContinueWith方法,我们可以配合TaskContinuationOptions枚举,得到更多我们想要的行为。

五、子任务

任务是支持父子关系的,即在一个任务中创建新任务。如下所示:

using System; 
using System.Threading.Tasks;

namespace TaskDemo 

    class ChildTask 
    { 
        static void Main() 
        { 
            Task parant = new Task(() => 
            { 
                new Task(() => Console.WriteLine("Hello")).Start(); 
                new Task(() => Console.WriteLine(",")).Start(); 
                new Task(() => Console.WriteLine("World")).Start(); 
                new Task(() => Console.WriteLine("!")).Start(); 
            }); 
            parant.Start(); 
            Console.ReadLine(); 
        } 
    } 
}

值得注意的是,以上代码中所示的子任务的调用并不是以代码的出现先后为顺序来调用的。

六、任务工厂

   在某些情况下,我们会遇到创建大量的任务,而恰好这些任务共用某个状态参数(如CancellationToken),为了避免大量的调用任务的构造器和一次又一次的参数传递,我们可以使用任务工厂来为我们处理这种大量创建工作。如下代码所示:

using System; 
using System.Threading; 
using System.Threading.Tasks;

namespace TaskDemo 

    public class FactoryOfTask 
    { 
        static void Main() 
        { 
            Task parent = new Task(() => 
            { 
                CancellationTokenSource cts = new CancellationTokenSource(); 
                TaskFactory tf = new TaskFactory(cts.Token); 
                var childTask = new[] 
                { 
                 tf.StartNew(()=>ConcreteTask(cts.Token)), 
                 tf.StartNew(()=>ConcreteTask(cts.Token)), 
                 tf.StartNew(()=>ConcreteTask(cts.Token)) 
                };

Thread.Sleep(5000);//此处睡眠等任务开始一定时间后才取消任务 
                cts.Cancel(); 
            } 
            );

parent.Start();//开始执行任务 
            Console.Read(); 
        }

static void ConcreteTask(CancellationToken token) 
        { 
            while (true) 
            { 
                if (!token.IsCancellationRequested) 
                { 
                    Thread.Sleep(500); 
                    Console.Write("."); 
                } 
                else 
                { 
                    Console.WriteLine("任务取消"); 
                    break; 
                } 
            } 
        } 
    } 
}

七、任务调度程序

任务的调度通过调度程序来实现的,目前,.NET 4.0内置两种任务调度程序:线程池任务调度程序(thread pool task scheduler)和同步上下文任务调度程序(synchronization context task scheduler)。默认情况下,应用程序使用线程池任务调度程序调用线程池的工作线程来完成任务,如受计算限制的异步操作。同步上下文任务调度程序通常使用UI线程来完成与Windows Forms,Windows Presentation Foundation(WPF)以及SilverLight应用程序相关的任务。

可喜的是,.NET 4.0 提供了TaskScheduler抽象类供开发人员继承来实现自定义任务调度程序的开发,有兴趣的同学可以试试。

八、总结

      任务给了我们更多的方便性、灵活性的同时,也带来了比线程池更多的资源消耗。如果想减少资源消耗,请直接使用线程池QueueUserWorkItem方法效果会更好;如果想要更多的控制与灵活性,任务(Task)是不二的选择。这个要我们开发者自己去斟酌了。

参考文献:《CLR Via C#》,Third edtion, 作者:Jeffrey Richer,726页-739页

《Introducing .NET 4.0 With Visual Studio 2010》,作者:Alex Mackey,106页-111页

http://www.cnblogs.com/myshell/archive/2010/03/23/1692059.html

.NET 4.0 任务(Task)的更多相关文章

  1. .Net4.0 任务(Task)[转]

    .Net4.0 任务(Task) 任务(Task)是一个管理并行工作单元的轻量级对象.它通过使用CLR的线程池来避免启动专用线程,可以更有效率的利用线程池.System.Threading.Tasks ...

  2. .Net4.0 任务(Task)

    任务(Task)是一个管理并行工作单元的轻量级对象.它通过使用CLR的线程池来避免启动专用线程,可以更有效率的利用线程池.System.Threading.Tasks 命名空间下任务相关类一览: 类 ...

  3. C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是TAP(Task-based Asynchronous Pattern, 基于任务的异步模式)

    学习书籍: <C#本质论> 1--C#5.0之后推荐使用TPL(Task Parallel Libray 任务并行库) 和PLINQ(Parallel LINQ, 并行Linq). 其次是 ...

  4. win10 Celery异步任务报错: Task handler raised error: ValueError('not enough values to unpack (expected 3, got 0)

    示例代码如下: from celery import Celery app = Celery('tasks', backend='redis://×××:6379/1', broker='redis: ...

  5. AFNetworking 3.0 源码解读(八)之 AFImageDownloader

    AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...

  6. 使用Spring Task轻松完成定时任务

    一.背景 最近项目中需要使用到定时任务进行库存占用释放的需求,就总结了如何使用Spring Task进行简单配置完成该需求,本文介绍Spring3.0以后自定义开发的定时任务工具, spring ta ...

  7. Spring任务调度器之Task的使用

    Spring Task提供两种方式进行配置,正如大家所想吧,还是一种是annotation(标注),而另外一种就是XML配置了.但其实这里我觉得比较尴尬,因为任务调度这样的需求,通常改动都是比较多的, ...

  8. 实践基于Task的异步模式

    Await 返回该系列目录<基于Task的异步模式--全面介绍> 在API级别,实现没有阻塞的等待的方法是提供callback(回调函数).对于Tasks来说,这是通过像ContinueW ...

  9. spring3使用task:annotation-driven开始定时

    先看一个案例 package com.jCuckoo.demo; import java.text.SimpleDateFormat; import java.util.Date; import or ...

随机推荐

  1. plsql 查询结果窗口 不正常

    今天发现了一个很有趣的现象,一个查询语句查出来的结果窗口只显示一部分. 是因为查询语句中有全角的字符或者空格: 如果是sqlServer的话直接就报错了,而plsql不报错,显示如下

  2. Java对象的序列化

    1.概念 序列化:把Java对象转换为字节序列的过程. 反序列化:把字节序列恢复为Java对象的过程. 2.用途 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个 ...

  3. 曲演杂坛--使用CTE时踩的小坑:No Join Predicate

    在一次系统优化中,意外发现一个比较“坑”的SQL,拿出来供大家分享. 生成演示数据: --====================================== --检查测试表是否存在 IF(O ...

  4. 烂泥:CentOS命令学习之scp复制

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 由于工作需要,需要把服务器A上的文件弄一份到服务器B上.自己比较懒不打算搭建FTP.Samba服务器,所以就打算使用scp命令,scp命令是通过ssh协 ...

  5. HTTP状态码分类说明

    状态码分类 HTTP状态码被分为五大类, 目前我们使用的HTTP协议版本是1.1, 支持以下的状态码.随着协议的发展,HTTP规范中会定义更多的状态码. 小技巧:  假如你看到一个状态码518, 你并 ...

  6. 注意Android里TextView控件的一个小坑,用android:theme来设置样式时动态载入的layout会丢失该样式

    注意Android里TextView控件的一个小坑,用android:theme来设置样式时动态载入的layout会丢失该样式 这个坑,必须要注意呀, 比如在用ListView的时候,如果在List_ ...

  7. 感觉没睡好就..-shenmedoumeixie....

    hi 昨晚没睡好,虽然梦很香,但睡不好没精神做科研啊... 1.jQuery 十二.实现聊天室创建 12.1 基本功能 登陆: 无刷新实时交流: 支持表情. 12.2 大致效果 登陆——>验证, ...

  8. .Net程序员之Python基础教程学习----列表和元组 [First Day]

    一. 通用序列操作: 其实对于列表,元组 都属于序列化数据,可以通过下表来访问的.下面就来看看序列的基本操作吧. 1.1 索引: 序列中的所有元素的下标是从0开始递增的. 如果索引的长度的是N,那么所 ...

  9. Unity3D中的线性插值Lerp()函数解析

    http://www.cnblogs.com/rongweijun/p/5739074.html

  10. Rhino 是一个完全使用Java语言编写的开源JavaScript实现。Rhino通常用于在Java程序中,为最终用户提供脚本化能力。它被作为J2SE 6上的默认Java脚本化引擎。

    https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino