在上篇中用了ThreadPool’s QueueUserWorkItem方法很简单的实现了.NET的多线程编程。但,用线程池是用限制的:线程池不会告诉你的方法是否完成;线程池也没有返回值。Task的出现有效地解决了这两个问题。
Task的功能很强大,一下列出Task的构造函数:
- Task(Action)
- Task(Action, CancellationToken)
- Task(Action, TaskCreationOptions)
- Task(Action<Object>, Object)
- Task(Action, CancellationToken, TaskCreationOptions)
- Task(Action<Object>, Object, CancellationToken)
- Task(Action<Object>, Object, TaskCreationOptions)
- Task(Action<Object>, Object, CancellationToken, TaskCreationOptions)
Action<Object>是指Task需要执行的操作;Object是执行操作所需要的参数;CancellationTaken支持协作取消,具体使用见这里。TaskCreationOptions是Task的运行方式,定义如下:
[Flags, Serializable]
public enum TaskCreationOptions
{
None = 0x0000,//the default
//提示 TaskScheduler 以一种尽可能公平的方式安排任务,
//这意味着较早安排的任务将更可能较早运行,
//而较晚安排运行的任务将更可能较晚运行。
PerferFairness = 0x0001,
//指定某个任务将是运行时间长、粗粒度的操作。 它会向 TaskScheduler 提示,
//过度订阅可能是合理的。
LongRunning = 0x0010,
//指定将任务附加到任务层次结构中的某个父级。
AttachedToParent = 0x0100,
}以下以Code为主。
以下例子简单的实现等待一个Task执行完毕并获得它的返回值。
泛型Task<TResult>派生自Task,支持返回一个TResult类型的值。
Task<int> t = new Task<int>((n) =>{int sum = 0;
for (int i = 0; i < (int)n; i++)sum += i;return sum;
}, 100);t.Start();t.Wait();Console.WriteLine(t.Result);
当然,是使用CancellationTokenSource来取消Task。
class Program
{static void Main(string[] args){CancellationTokenSource ts = new CancellationTokenSource();
Task<int> t = new Task<int>(() => Sum(ts.Token, 1000));t.Start();Console.WriteLine("<ENTER> to Cancel");
Console.ReadKey();ts.Cancel();Console.WriteLine("Be Canceled");
Console.ReadKey();}static int Sum(CancellationToken token, int n){int sum = 0;
for (int i = 0; i < (int)1000; i++){token.ThrowIfCancellationRequested();sum += i;Thread.Sleep(200);}return sum;
}}注意:如果,你在Task被Cancel之后,试着去读Task的Result,Task会报出AggregateException。但,实际中token.ThrowIfCancellationRequested()抛出的Exception是OperationCanceledException。抛出AggregateException的原因是:当Task中有未处理Exception时,不会抛出,被吞没,存在一个集合中,并返回给线程池。当主线程中调用Wait方法或者使用Result属性是火,会抛出AggregateException。
为了主线程的流畅,不能调用Wait方法及一直查询Result属性,这样会产生阻塞。可以在一个Task完成时自动运行一个新Task就能有效的解决该问题,这就使用到了Task的ContinueWith方法。
CancellationTokenSource ts = new CancellationTokenSource();
Task<int> t = new Task<int>(() => Sum(ts.Token, 1000));t.Start();t.ContinueWith(task => Console.WriteLine(task.Result));MainThreadDoingOtherThings();
ContinueWith有很多重载方法,其中有
public Task ContinueWith( Action<Task> continuationAction, TaskContinuationOptions continuationOptions )TaskContinuationOptions 定义如下:[Flags, Serializable]public enum TaskContinuationOptions{None = 0x0000,//The default//提示 TaskScheduler 以一种尽可能公平的方式安排任务,//这意味着较早安排的任务将更可能较早运行,//而较晚安排运行的任务将更可能较晚运行。PreferFairness = 0x0001,//指定某个任务将是运行时间长、粗粒度的操作。//它会向 TaskScheduler 提示,过度订阅可能是合理的。LongRunning = 0x0002,//指定将任务附加到任务层次结构中的某个父级。AttachedToParent = 0x0004,//指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。//此选项对多任务延续无效。NotOnRanToCompletion = 0x1000,//指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。//此选项对多任务延续无效。NotOnFaulted = 0x2000,//指定不应在延续任务前面的任务已取消的情况下安排延续任务。//此选项对多任务延续无效。NotOnCanceled = 0x4000,//指定应同步执行延续任务。 指定此选项后,延续任务将在导致前面的//任务转换为其最终状态的相同线程上运行。ExecuteSynchronously = 0x8000,//OnlyOnRanToCompletion = NotOnCanceled | NotOnFaultedOnlyOnFaulted = NotOnRanToCompletion | NotOnCanceledOnlyOnCanceled =NotOnRanToCompletion | NotOnFaulted}
Task<Int32[]> parent = new Task<int[]>(() =>{var Result = new Int32[3];
new Task(() => Result[0] = Sum(100), TaskCreationOptions.AttachedToParent).Start();
new Task(() => Result[1] = Sum(100), TaskCreationOptions.AttachedToParent).Start();
new Task(() => Result[2] = Sum(100), TaskCreationOptions.AttachedToParent).Start();
return Result;
});parent.ContinueWith(task => Array.ForEach(task.Result, Console.WriteLine));parent.Start();
Task内部主要注意下TaskStatus属性,表示 Task 的生命周期中的当前阶段。定义如下:
public enum TaskStatus
{
Created,
WaitingForActivation,
WaitingToRun,
Running,
WaitingForChildrenToComplete,
RanToCompletion,
Canceled,
Faulted
}
假如你想要创建一打相同配置的Task,你就会用到System.Threading.Tasks命名空间下的TaskFactory类型或者TaskFactory<TResult>。
简单例子:
Task parent = new Task(() =>
{var cts = new CancellationTokenSource();
var tf = new TaskFactory<Int32>(cts.Token, TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
var childTasks = new[] {
tf.StartNew(()=>Sum(cts.Token,100)),tf.StartNew(()=>Sum(cts.Token,1020)),tf.StartNew(()=>Sum(cts.Token,1040)),};for (int task = 0; task < childTasks.Length; task++){childTasks[task].ContinueWith(t => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);}tf.ContinueWhenAll(childTasks, completedTasks => completedTasks.Where(t => !t.IsCanceled && !t.IsFaulted).Max(t => t.Result), CancellationToken.None).ContinueWith(t => Console.WriteLine(t.Result), TaskContinuationOptions.ExecuteSynchronously);});nt.Start();
该实例中,使用一个TaskFactory<Int32>来创建配置相同的三个Task。三个Task共享一个CancellationTokenSource的Token,都是parent的子Task,continue的Task都是以ExecuteSynchronously方式执行,都使用默认的TaskScheduler。
FCL提供两个派生自TaskSchedule的类型:线程池的TaskSchedule和同步上下文TaskSchedule。默认地,所有应用程序使用前者。后者主要适用于Windows Forms、WPF、Silverlight 应用程序。可以用于更新UI组件。可以从TaskSchedule的静态方法FromCurrentSynchronizationContext获得同步上下文TaskSchedule。
简单的例子:在WinForm中使用FromCurrentSynchronizationContext,在Form中放一个Button和一个Label。
private CancellationTokenSource cts;
private void button1_Click(object sender, EventArgs e){if (cts != null){cts.Cancel();cts = null;
button1.Text = "Operation Canceled!";
}else
{button1.Text = "Operation running...";
cts = new CancellationTokenSource();
var task = new Task<int>((token) => Sum(cts.Token, 1000), cts);task.Start();task.ContinueWith(t => label1.Text = "Result: " + t.Result, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task.ContinueWith(t => label1.Text = "Operation Canceled", CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());
task.ContinueWith(t => label1.Text = "Operation faulted", CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}}