任务(Task)是一个管理并行工作单元的轻量级对象。它通过使用CLR的线程池来避免启动专用线程,可以更有效率的利用线程池。System.Threading.Tasks 命名空间下任务相关类一览:
类 | 作用 |
Task | 管理工作单元 |
Task<TResult> | 管理带返回值的工作单元 |
TaskFactory | 创建任务 |
TaskFactory<TResult> | 创建任务或者有相同返回值的延续任务 |
TaskScheduler | 管理任务调度 |
TaskCompletionSource | 手动控制任务工作流 |
任务用来并行地执行工作,充分地利用多核:事实上,Parallel和PLINQ内部就是建立在任务并行的结构上。
任务提供了一系列强大的特性来管理工作单元,包括:
同时任务实现了一个本地工作队列,它允许你高效地创建快速执行的子任务而不用遭受在单个工作队列时的竞争花费。任务并行库让你用最小的花费来创建成百上千的任务,但是如果你想创建上百万个任务,就必须分割这些任务到更大的工作单元,以保持效率。
有两种方法可以创建任务,一种是通过TaskFactory的StartNew()方法创建并启动任务;另一种是调用Task构造函数创建,然后手动启动任务。需要注意的是,任务启动后并不会立即执行,它是由任务调度器(TaskScheduler)来管理的。
// 没有返回值
Task.Factory.StartNew(() => Console.WriteLine( " Task Created! " ));
// 有返回值
var task = Task.Factory.StartNew < string > (() => " Task Created! " );
Console.WriteLine(task.Result);
var task = new Task < string > (() => " Task Created! " );
task.Start(); // 异步执行
Console.WriteLine(task.Result);
var task = new Task < string > (() => " Task Created! " );
task.RunSynchronously(); // 同步执行
Console.WriteLine(task.Result);
也可以在创建任务时指定一个任务状态参数,可以通过任务的AsyncState属性来访问该参数。示例:
var task = Task.Factory.StartNew(state => " hello " + state, " Mike " );
Console.WriteLine(task.AsyncState);
Console.WriteLine(task.Result);
你还可以指定一个任务创建选项(TaskCreationOptions) ,这个枚举类型有以下枚举值:None,LongRunning,PreferFairness,AttachedToParent。下面解释各个枚举值的作用。
第一种方式:
var parent = Task.Factory.StartNew(() =>
{
var nonChildTask = Task.Factory.StartNew(
() => Console.WriteLine( " I'm not a child task. " )
);
var childTask = Task.Factory.StartNew(
() => Console.WriteLine( " I'm a child task. " ),
TaskCreationOptions.AttachedToParent);
});
第二种方式:
Task parent = new Task(() =>
{
DoStep1();
});
Task task2 = parent.ContinueWith ((PrevTask) =>
{
DoStep2();
});
parent.Start();
任务可以通过Wait()成员方法或Result属性来等待任务完成。
当调用Result属性时,将会执行下列操作:
Task.WaitAny()静态方法等待任何一个任务完成。示例:
var tasks = new Task[ 3 ];
for ( int i = 0 ; i < tasks.Length; i ++ )
{
int taskIndex = i;
tasks[i] = Task.Factory.StartNew(() =>
{
int seed = Guid.NewGuid().GetHashCode();
int waitTime = new Random(seed).Next( 10 , 100 );
Thread.Sleep(waitTime);
Console.WriteLine( " Task{0} Finished " , taskIndex);
});
}
Task.WaitAny(tasks);
Console.WriteLine( " 已有任务完成 " );
Task.WaitAll()静态方法等待所有任务完成。即使有任务抛出异常也不会终止等待,它会在所有任务完成之后抛出一个AggregateException异常,这个异常聚合了所有任务抛出的异常。示例:
var tasks = new Task[ 3 ];
for ( int i = 0 ; i < tasks.Length; i ++ )
{
int taskIndex = i;
tasks[i] = Task.Factory.StartNew(() =>
{
int waitTime = new Random(Guid.NewGuid().GetHashCode()).Next( 10 , 100 );
Thread.Sleep(waitTime);
Console.WriteLine( " Task{0} Finished " , taskIndex);
});
}
Task.WaitAll(tasks);
Console.WriteLine( " 所有任务完成 " );
默认情况下任务未处理的异常会终止应用程序。需要指出的是任务中未处理的异常不会立即导致应用程序终止,异常要延迟到垃圾回收器回收任务并调用Finalize方法时才会终止程序。如果读取了任务的Exception属性,这个操作将阻止随后的应用程序终止。
当等待任务完成时,所有未处理的异常会传递到调用方。
Wait()方法超时的异常也必须处理,否则导致应用程序终止。
子任务中未处理的异常会冒泡传递到父任务;嵌套任务中的非子任务的异常不会传递到这个任务的上一层任务,需要单独处理,否则将导致应用程序终止。
var task = Task.Factory.StartNew(() =>
{
Task.Factory.StartNew(() => { throw null ; }, TaskCreationOptions.AttachedToParent);
Task.Factory.StartNew(() => { throw null ; }, TaskCreationOptions.AttachedToParent);
Task.Factory.StartNew(() => { throw null ; }, TaskCreationOptions.AttachedToParent);
});
task.Wait();
TaskScheduler.UnobservedTaskException静态事件提供了最后一种手段处理所有未处理异常。通过处理这个事件,就不用终止应用程序,而用你自己的异常处理逻辑替代它。
当创建任务时,可以传入一个取消令牌(CancelationToken)参数,这样就可以安全的取消任务。示例:
var source = new CancellationTokenSource();
var token = source.Token;
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine( " Task starting... " );
while ( true )
{
token.ThrowIfCancellationRequested();
Console.WriteLine( " I'm alive. {0} " ,DateTime.Now);
Thread.Sleep( 1000 );
}
},token);
Task.Factory.StartNew(() =>
{
Thread.Sleep( 4500 );
source.Cancel();
});
try
{
task.Wait();
Console.WriteLine( " Task stopped. " );
}
catch (AggregateException e)
{
if (e.InnerException is OperationCanceledException)
{
Console.WriteLine( " Task canceled. " );
}
else
{
Console.WriteLine( " errors. " );
}
}
通过调用CancellationTokenSource的Cancel()方法取消任务,这个并不会立即终止任务,一直延迟到任务下次检测是否取消时才通过抛出OperationCanceledException终止任务。
如果想通过直接抛出OperationCanceledException异常的方式取消任务,则需要在任务中传入CancelationToken参数,否则就不能将任务的状态为TaskStatus.Canceled并触发OnlyOnCanceled延续任务。
此外,取消令牌也可以传递到Wait和CancelAndWait方法中来取消等待。