zoukankan      html  css  js  c++  java
  • 使用Task代替ThreadPool和Thread

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

    一:Task的优势

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

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

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

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

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

    复制代码
    staticvoid Main(string[] args)
    {
    Task t =new Task(() =>
    {
    Console.WriteLine("任务开始工作……");
    //模拟工作过程
    Thread.Sleep(5000);
    });
    t.Start();
    t.ContinueWith((task) =>
    {
    Console.WriteLine("任务完成,完成时候的状态为:");
    Console.WriteLine("IsCanceled={0} IsCompleted={1} IsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
    });
    Console.ReadKey();
    }
    复制代码


    二:Task的完成状态

    任务Task有这样一些属性,让我们查询任务完成时的状态:

    1: IsCanceled,因为被取消而完成;

    2: IsCompleted,成功完成;

    3: IsFaulted,因为发生异常而完成

    需要注意的是,任务并没有提供回调事件来通知完成(像BackgroundWorker一样),它通过启用一个新任务的方式来完成类似的功能。ContinueWith方法可以在一个任务完成的时候发起一个新任务,这种方式天然就支持了任务的完成通知:我们可以在新任务中获取原任务的结果值。

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

    复制代码
    staticvoid 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();
    }

    staticvoid TaskEnded(Task<int> task)
    {
    Console.WriteLine("任务完成,完成时候的状态为:");
    Console.WriteLine("IsCanceled={0} IsCompleted={1} IsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
    Console.WriteLine("任务的返回值为:{0}", task.Result);
    }

    staticint 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方法进行处理。如果采用后者的方式,如下:

    复制代码
    staticvoid 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();
    }

    staticvoid TaskEndedByCatch(Task<int> task)
    {
    Console.WriteLine("任务完成,完成时候的状态为:");
    Console.WriteLine("IsCanceled={0} IsCompleted={1} IsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
    try
    {
    Console.WriteLine("任务的返回值为:{0}", task.Result);
    }
    catch (AggregateException e)
    {
    e.Handle((err) => err is OperationCanceledException);
    }
    }

    staticint 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)
    {
    thrownew Exception("error");
    }
    result++;
    Thread.Sleep(1000);
    }
    复制代码

    模拟异常后的输出为:

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


    三:任务工厂

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

    复制代码
    staticvoid 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();
    }

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

    以上代码输出为:

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

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

  • 相关阅读:
    UVa 10118 记忆化搜索 Free Candies
    CodeForces 568B DP Symmetric and Transitive
    UVa 11695 树的直径 Flight Planning
    UVa 10934 DP Dropping water balloons
    CodeForces 543D 树形DP Road Improvement
    CodeForces 570E DP Pig and Palindromes
    HDU 5396 区间DP 数学 Expression
    HDU 5402 模拟 构造 Travelling Salesman Problem
    HDU 5399 数学 Too Simple
    CodeForces 567F DP Mausoleum
  • 原文地址:https://www.cnblogs.com/bincoding/p/7550114.html
Copyright © 2011-2022 走看看