多核时代 .NET Framework 4 中的并行编程7---任务工厂和任务计划
1. 任务工厂TaskFactory
提供对创建和计划 Task 对象的支持.其中TaskFactory提供了以下几种构造函数:
Ø TaskFactory() 使用默认配置初始化 TaskFactory 实例。
Ø TaskFactory(CancellationToken) 使用指定配置初始化 TaskFactory 实例,使用此构造函数,我们就可以在需要的时候取消相应的并行计算.
Ø TaskFactory(TaskScheduler) 使用指定配置初始化 TaskFactory 实例。
Ø TaskFactory(TaskCreationOptions, TaskContinuationOptions) 使用指定配置初始化 TaskFactory 实例。
其中TaskCreationOptions枚举可选择以下几种之一:
(1) None 指定应使用默认行为。
(2) PreferFairness 提示 TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
(3) LongRunning 指定某个任务将是运行时间长、粗粒度的操作。它会向 TaskScheduler 提示,过度订阅可能是合理的。
(4) AttachedToParent 指定将任务附加到任务层次结构中的某个父级。
TaskContinuationOptions是为通过使用 ContinueWith 或 ContinueWith 方法创建的连续任务指定行为,可以选择以下枚举几种之一:
(1) None 默认情况下,完成前面的任务之后将安排运行延续任务,而不考虑前面任务的最终 TaskStatus。
(2) PreferFairness 提示 TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
(3) LongRunning 指定某个任务将是运行时间长、粗粒度的操作。 它会向 TaskScheduler 提示,过度订阅可能是合理的。
(4) AttachedToParent 指定将任务附加到任务层次结构中的某个父级。
(5) NotOnRanToCompletion 指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。
(6) NotOnFaulted 指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。 NotOnCanceled 指定不应在延续任务前面的任务已取消的情况下安排延续任务。
(7) OnlyOnRanToCompletion 指定只应在延续任务前面的任务已完成运行的情况下才安排延续任务。
(8) OnlyOnFaulted 指定只应在延续任务前面的任务引发了未处理异常的情况下才安排延续任务。
(9) OnlyOnCanceled 指定只应在延续任务前面的任务已取消的情况下才安排延续任务。
(10) ExecuteSynchronously 指定应同步执行延续任务。指定此选项后,延续任务将在导致前面的任务转换为其最终状态的相同线程上运行。 如果在创建延续任务时已经完成前面的任务,则延续任务将在创建此延续任务的线程上运行。只应同步执行运行时间非常短的延续任务。
Ø TaskFactory(CancellationToken, TaskCreationOptions, TaskContinuationOptions, TaskScheduler) 使用指定配置初始化 TaskFactory 实例。
TaskFactory创建任务示例代码如下:
1) 直接创建并启动任务:
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine("DateTime is {0}", DateTime.Now);
});
2) 创建并启动可以取消的任务:
var canceltoken = new CancellationTokenSource();
var token = canceltoken.Token;
var task = Task.Factory.StartNew(() =>
{
for (int i = 0; i < 1000; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("CancellationRequested!");
throw new OperationCanceledException(token);
}
else
{
Console.WriteLine("i={0}", i);
Thread.Sleep(1 * 1000);
}
}
}, token);
canceltoken.Cancel();
2. TaskScheduler
表示一个处理将任务排队到线程中的低级工作的对象. TaskScheduler是个抽象类,我们可以根据需要通过继承TaskScheduler类,来实现自己的任务计划.
3. 延续任务.
在异步编程中,一个异步操作在完成时调用另一个操作并将数据传递到其中的情况非常常见。 传统上,此过程是通过使用回调方法完成的。 在任务并行库中,“延续任务”提供了同样的功能。 延续任务(也简称为“延续”)是一个异步任务,由另一个任务(称为“前面的任务”)在完成时调用。
尽管延续相对容易使用,但也十分强大和灵活。 例如,您可以:
Ø 将数据从前面的任务传递到延续
Ø 指定将调用或不调用延续所依据的精确条件
Ø 在延续启动之前取消延续,或在延续正在运行时以协作方式取消延续
Ø 提供有关应如何安排延续的提示
Ø 从同一前面的任务中调用多个延续
Ø 在多个前面的任务中的全部或任意任务完成时调用一个延续
Ø 将延续依次相连,形成任意长度
Ø 使用延续来处理前面的任务所引发的异常
1) 通过使用 Task.ContinueWith 方法创建延续. 例如:
Task firstGen = new Task(() =>
{
Console.WriteLine("Message from first generation task");
throw new Exception();
});
Task secondGen1 = firstGen.ContinueWith(antecedent =>
{
Console.WriteLine("Antecedent task faulted with type:{0}", antecedent.Exception.GetType());
}, TaskContinuationOptions.OnlyOnFaulted);
Task secondGen2 = firstGen.ContinueWith(antecedent =>
{
Console.WriteLine("Antecedent task NOT faulted");
}, TaskContinuationOptions.NotOnFaulted);
firstGen.Start();
Console.WriteLine("Press enter to finish");
Console.ReadLine();
上面的代码通过ContinueWith方法创建延续任务.在创建完延续任务后,我们调用第1个任务的Start()方法来开始任务的执行(如上面代码的firstGen.Start();).
2) ContinueWith<T>创建带返回值的延续任务。关于返回值,需要说明的是:
Ø 当父任务和延续的子任务都没有返回值时,则可使用Task.Factory.ContinueWhenAll()和Task.Factory.ContinueWhenAny()创建延续任务。
Ø 当父任务没有返回值,而延续的子任务都有返回值时,则可Task<T>.Factory.ContinueWhenAll()和Task<T>.Factory.ContinueWhenAny()创建延续任务。
Ø 当父任务有返回值,而延续的子任务没有返回值时,则可Task.Factory.ContinueWhenAll<T>()和Task.Factory.ContinueWhenAny<T>()创建延续任务。
Ø 当父任务和延续的子任务都有返回值时,则可Task<T>.Factory.ContinueWhenAll<V>()和Task<T>.Factory.ContinueWhenAny<V>()创建延续任务。
代码如下:
class BankAccount
{
public int Balance
{
get;
set;
}
}
Task<BankAccount> rootTask = new Task<BankAccount>(() =>
{
BankAccount account = new BankAccount();
for (int i = 0; i < 1000; i++)
{
account.Balance++;
}
return account;
});
Task<int> continuationTask1 = rootTask.ContinueWith<int>((Task<BankAccount> antecedent) =>
{
Console.WriteLine("Interim Balance 1:{0}", antecedent.Result.Balance);
return antecedent.Result.Balance * 2;
});
Task continuationTask2 = continuationTask1.ContinueWith((Task<int> antecedent) =>
{
Console.WriteLine("Final balance 1:{0}", antecedent.Result);
});
rootTask.ContinueWith<int>((Task<BankAccount> antecedent) =>
{
Console.WriteLine("Interim Balance 2:{0}", antecedent.Result.Balance);
return antecedent.Result.Balance / 2;
}).ContinueWith((Task<int> antecedent) =>
{
Console.WriteLine("Final Balance 2:{0}", antecedent.Result);
});
rootTask.Start();
Console.WriteLine("Press enter to finish");
Console.ReadLine();
3) ContinueWhenAll创建延续任务,等待一组任务中全部都完成时执行延续任务。ContinueWhenAny创建延续任务,等待一组任务中任何一个任务完成时执行延续任务。代码如下:
BankAccount account = new BankAccount();
Task<int>[] tasks = new Task<int>[10];
for (int i = 0; i < 10; i++)
{
tasks[i] = new Task<int>((stateObjectt) =>
{
int isolatedBalance = (int)stateObjectt;
for (int j = 0; j < 1000; j++)
{
isolatedBalance++;
}
return isolatedBalance;
}, account.Balance);
}
Task continuation = Task.Factory.ContinueWhenAll<int>(tasks, antecedents =>
{
foreach (Task<int> t in antecedents)
{
account.Balance += t.Result;
}
});
foreach (Task t in tasks)
{
t.Start();
}
try
{
continuation.Wait();
}
catch (AggregateException agex)
{
agex.Flatten().Handle(inner =>
{
Console.WriteLine("Handled Exception of type:{0}", inner);
return true;
});
}
Console.WriteLine("Expected value {0}, Balance: {1}", 10000, account.Balance);
Console.WriteLine("Press enter to finish");
Console.ReadLine();
同样,在创建可以取消任务(使用CancellationTokenSource类)和异常处理方式(使用AggregateException类),都是类似的。在此就不重复说。
4. TaskFactory和TaskScheduler
TaskFactory和TaskScheduler的关系是:任务工厂负责任务Task的创建,而任务计划则负责相关任务有条不紊的执行调度.