Thread的问题

  • 线程(Thread)时用来创建并发(concurrency)的一种低级别工具,它有一些限制,尤其是:
    • 虽然开始线程的时候可以方便的传入数据,但是当Join的时候,很难从线程获得返回值。
      • 可能需要设置一些共享字段。
      • 如果操作抛出异常,捕获和传播该异常都很麻烦。
    • 无法告诉线程在结束时开始做另外的工作,你必须进行Join操作(在进程中阻塞当前的线程)
  • 很难使用较小的并发(concurrent)来组建大型的并发。
  • 导致了对手动同步的更大依赖以及随之而来的问题。

Task Class

  • Task类可以很好的解决上述问题
  • Task是一个相对高级的抽象:它代表了一个并发操作(concurrent)
    • 该操作可能由Thread支持,或不由Thread支持
  • Task是可组合的(可使用Continuation)把它们串成链
    • Tasks可以使用线程池来减少启动延迟
    • 使用TskCompletionSource,Tasks可以利用回调的方式,在等待I/O绑定时完全避免线程。

开始一个Task => Task.Run

  • Task类在System.Threading.Tasks命名空间下。
  • 开始一个Task最简单的办法就是使用Task.Run(.NET 4.5,4.0的时候时使用Task.Factory.StartNew)这个静态方法:

    • 传入一个Action委托即可
      1. class Program
      2. {
      3. static void Main(string[] args)
      4. {
      5. Task.Run(() => { Console.WriteLine("Foo"); });
      6. }
      7. }
  • Task模式使用线程池,也就是后台线程

    • 当主线程结束的时候,你创建的所有Tasks都会结束。
      1. class Program
      2. {
      3. static void Main(string[] args)
      4. {
      5. // 后台线程
      6. Task.Run(() => { Console.WriteLine("Foo"); });
      7. // 因为上面示例中,主线程走完了,控制台还没有执行输出,这使用ReadKey达到阻塞的目的。
      8. Console.ReadKey();
      9. }
      10. }
  • Task.Run返回一个Task对象,可以使用它来监视其过程

    • 在Task.Run之后,我们没有调用Start,因为该方法创建的是“热”任务(hot task)
      • 可以通过Task的构造函数创建“冷”任务(cold task),但是很少这样做
  • 可以通过Task的Status属性来跟踪task的执行状态。

Wait => 等待

  • 调用Task的Wait方法会进行阻塞直到操作完成

    • 相当于调用Thread上的Join方法
    • 例子

      1. class Program
      2. {
      3. static void Main(string[] args)
      4. {
      5. Task task = Task.Run(() =>
      6. {
      7. Thread.Sleep(3000);
      8. Console.WriteLine("Foo");
      9. });
      10. Console.WriteLine(task.IsCompleted); // false
      11. // 阻塞直至 task 完成操作
      12. task.Wait();
      13. Console.WriteLine(task.IsCompleted);// true
      14. }
      15. }
  • Wait也可以让你指定一个超时时间和一个取消令牌来提前结束等待。

Long-running tasks => 长时间运行的任务

  • 默认情况下,CLR在线程池中运行Task,这非常适合短时间运行的Compute-Bound类工作。
  • 针对长时间运行的任务或阻塞操作(前面的例子),你可以不采用线程池,例子。

    1. class Program
    2. {
    3. static void Main(string[] args)
    4. {
    5. Task task = Task.Factory.StartNew(() =>
    6. {
    7. Thread.Sleep(3000);
    8. Console.WriteLine("Foo");
    9. }, TaskCreationOptions.LongRunning);
    10. }
    11. }
  • 如果同时运行多个long-running tasks(尤其是其中有处于阻塞状态的),那么性能将会受很大影响,这时有比TaskCreationOptions.LongRunning更好的办法:

    • 如果任务是IO-Bound,TaskCompletionSource和异步函数可以让你用回调(Coninuations)代替线程来实现并发。
    • 如果任务是Comptre-Bound,生产者/消费者队列允许你对任务的并发性进行限流,避免把其他线程和进程饿死。