26.5 任务
调用ThreadPool的QueueUserWorkItem方法发起一次异步的、受计算限制的操作时非常简单的。然而,这个技术存在很多限制。最大的问题是没有一个内建的机制让你知道操作在什么时候完成,也没有一个机制在操作完成时获得一个返回值。为了克服这些限制(并解决其他一些问题),Microsoft引入了任务(task)的概念。我们通过System.Threading.Tasks命名空间中的类型来使用它们。
26.5.1 等待任务完成并获取它的结果
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadStudy
{
class Program
{
static void Main(string[] args)
{
//创建一个Task
Task<int> t = new Task<int>(n => Sum(100),CancellationToken.None);
//可以以后再启动任务
t.Start();
//可以显式地等待任务完成
t.Wait();
//可以获得结果(Result属性内部会调用Wait)
Console.WriteLine("The sum is:" + t.Result);
}
static int Sum(int num)
{
Thread.Sleep(100);
if (num == 0)
return 0;
else
return num + Sum(num - 1);
}
}
}
The sum is:5050
Press any key to continue . . .
如果计算限制的任务抛出一个未处理的异常,这个异常会被“侵吞”并存储到一个集合中,而线程池线程允许返回到线程池中。调用Wait方法或者Result属性时,这些成员会抛出一个System.AggregateException对象。
一个线程调用Wait方法时,系统会检查线程要等待的Task是否已开始执行。如果是,调用Wait的线程会阻塞,直到Task运行结束为止。但是,如果Task还没有开始执行,系统可能(取决于TaskScheduler)使用调用Wait的线程来执行Task。如果发生这种情况,那么调用Wait的线程不会阻塞;它会执行Task并立即返回。
AggregateException类型用于封闭异常对象的一个集合。
除了等待单个任务,Task类还提供了两个静态方法,它们允许线程等待一个Task对象数组。其中,Task的静态WaitAny方法会阻塞调用线程,直到数组中的任何一个Task对象完成。这个方法返回一个Int32数组索引值,指明完成的是哪一个Task对象。方法返回后,线程被唤醒并继续执行。如果发生超时,方法返回-1.如果WaitAny通过一个CancellationToken取消,会抛出一个OperationCanceledException。
类似地,Task类还有一个静态WaitAll方法,它阻塞调用线程,直到数组中的所有Task对象都完成。如果所有Task对象都完成,WaitAll方法返回true。如果发生超时,则返回false。如果WaitAll通过CancellationToken而取消,会抛出一个OperationCanceledException。
假如一直不调用Wait或Result,或者一直不查询Task的Exception属性,你的代码就永远注意不到这个异常的发生。这当然不好,因为程序遇到一个未料到的问题,而你竟然没有注意到。所以,当Task对象在垃圾回收时,它的Finalize方法会检查Task是否遇到了一个没有被注意到的异常;如果是,Task的Finalize方法就抛出AggregateException。由于不能捕捉由CLR的终结器方法抛出的异常,所以进程会立即终止。必须调用前面提到的某个成员,确保代码注意到异常并从异常中恢复。