使用ThreadPool发起一次异步的、受计算限制的操作是非常简单的,但是没有一个机制在任务结束后获得一个返回值,为了克服这些问题,微软引入了任务(task)的概念。创建Task的方式总是调用构造器。
重要提示:一个线程调用Wait方法时,系统检测要等待的Task是否已经开始执行,如果是,调用wait的线程会被阻塞,直到task运行结束。但是task还没有开始执行,系统可能使用调用wait的线程来执行task。如果发生这种情况,调用wait的线程不会被阻塞,它会执行task并立即返回。这样的好处在于没有线程被阻塞,减少资源的使用,并提升性能。但不好的地方在于,假如线程在调用wait前已经获得一个线程同步锁,而task试图获取同一个锁,就会造成死锁!
Task类还提供两个静态方法,他们允许等待一个task对象数组。WaitAny方法会阻塞调用线程,直到数组中的任何一个task对象完成。这个方法返回一个int32数组索引值,指明完成哪个task对象。方法返回后,线程 被唤醒并继续执行。类似的,WaitAll方法阻塞调用线程,直到数组中所有task对象都完成,它返回true。
重要提示:如果一直不调用wait或Result,或者一直不查询task的Exception属性,你的代码永远不知道这个异常的发生,当task对象在垃圾回收时,它的Finalize方法会检查Task是否遇到一个没有被注意到的异常,由于不能捕获由CLR终结器方法抛出的异常,所以进程会立即终止。
取消任务 可以使用CancellationTokenSource取消一个任务,任务关联的方法需要一个CancellationToken的实例对象,然后在方法内部调用ThrowIfCancellationRequested()。
一个任务完成时自动启动另一个任务
// Create and start a Task, continue with another task
Task<Int32> t = Task.Run(() => Sum(CancellationToken.None, 10000));
// ContinueWith returns a Task but you usually don't care
Task cwt = t.ContinueWith(task => Console.WriteLine("The sum is: " + task.Result));
任务可以启动子任务
Task<Int32[]> parent = new Task<Int32[]>(() => {
var results = new Int32[3]; // Create an array for the results
// This tasks creates and starts 3 child tasks
new Task(() => results[0] = Sum(10000), TaskCreationOptions.AttachedToParent).Start();
new Task(() => results[1] = Sum(20000), TaskCreationOptions.AttachedToParent).Start();
new Task(() => results[2] = Sum(30000), TaskCreationOptions.AttachedToParent).Start();
// Returns a reference to the array (even though the elements may not be initialized yet)
return results;
});
// When the parent and its children have run to completion, display the results
var cwt = parent.ContinueWith(
parentTask => Array.ForEach(parentTask.Result, Console.WriteLine));
// Start the parent Task so it can start its children
parent.Start();
默认情况下,一个任务创建的task对象是顶级任务,与创建他们的那个任务无关。然而通过TaskCreationOptions.AttachedToParent关联起来。结果是,除非子任务全部结束运行,否则父任务不会认为已经结束