我有一个任务列表,很少有任务依赖于其他任务,我想现在运行任务,如果执行过程中任何任务失败,需要停止所有正在运行的任务并关闭应用程序。
如何用TPL做到这一点? 如何停止正在运行的任务? 我需要优化以下代码。
详细要求 - 作为任务启动登录屏幕。 o仅在登录成功时并行运行所有其他任务。 o在登录失败或取消时退出应用程序 - 如果任何任务失败,退出应用程序
var done = new List<TaskDetails>();
var executing = new List<TaskDetails>();
var unblocked = new List<TaskDetails>();
var blocked = new List<TaskDetails>();
foreach (var startupTest in startupTests) {
if (startupTest.DependsOn == null) {
unblocked.Add(startupTest);
} else {
blocked.Add(startupTest);
}
}
IDictionary<int, TaskDetails> tasksByID = new Dictionary<int, TaskDetails>();
var tasksTPL = new Task<object>[startupTests.Count];
var taskCount = 0;
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
while (done.Count < startupTests.Count) {
while (executing.Count < config.MaximumConcurrency && unblocked.Count > 0) {
TaskDetails nextTask = unblocked[0];
lock (syncLock) {
unblocked.Remove(nextTask);
executing.Add(nextTask);
}
// Execute
try {
var method = GetMethod(
nextTask.AssemblyName, nextTask.ClassName, nextTask.MethodName
);
if (method == null) {
throw new Exception("Method" + nextTask.MethodName + " not available.");
}
tasksTPL[taskCount] =
Task<object>.Factory.StartNew(() => method.Invoke(null, null),
cancellationToken);
tasksByID.Add(tasksTPL[taskCount].Id, nextTask);
tasksTPL[taskCount].ContinueWith(tsk => {
lock (syncLock) {
done.Add(tasksByID[tsk.Id]);
executing.Remove(tasksByID[tsk.Id]);
}
if (tsk.Exception != null) {
TraceAlways(
"Caught Exception while running startuptest: " +
tsk.Exception
);
}
});
taskCount++;
} catch (TargetInvocationException e) {
TraceAlways(
"Failed running " + nextTask.MethodName + " method." + e.Message);
}
}
Task.WaitAny(tasksTPL.Where(task => task != null).ToArray());
var toRemove = new List<TaskDetails>();
lock (syncLock) {
List<string> doneTaskName =
done.Select(TaskDetails => TaskDetails.Name).ToList();
foreach (var task in blocked) {
bool isBlocked = task.DependsOn.Any(dep => !doneTaskName.Contains(dep));
if (!isBlocked) {
toRemove.Add(task);
unblocked.Add(task);
}
}
foreach (var TaskDetails in toRemove) {
blocked.Remove(TaskDetails);
}
}
if (executing.Count == 0 && unblocked.Count == 0 && blocked.Count > 0) {
throw new Exception("Cyclic Dependency");
}
}
taskCount = 0;
foreach (var task in tasksTPL) {
if (
(task.Status != TaskStatus.Faulted) &&
(task.Result is bool) &&
(!(bool)task.Result)
) {
TraceAlways("Startup Test" + startupTests[taskCount].MethodName + " failed.");
if (startupTests[taskCount].ShowNotification) {
cancellationTokenSource.Cancel();
MessageBox.Show(
"An error has accoured. See log for more details.", "Startup Error"
);
}
Environment.Exit(0);
break;
}
taskCount++;
}
答案 0 :(得分:1)
以下是我如何在概念上实施它(如果我正确理解了问题),尽管我没有尝试满足您的所有详细要求。
ContinueWith
。DoTaskAsync
是一项独立的任务。 DoTaskSequenceAsync
是一系列任务,其中一些任务取决于其他任务的结果。 BadTaskAsync
是投掷任务的一个示例,其失败应取消所有其他待处理任务。WrapAsync
用try/catch
包装任务以捕获任务的异常并从内部提升全局取消。您可以编译并将其作为控制台应用程序进行尝试。
using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
namespace MultipleTasks
{
class Program
{
class Worker
{
// a single async Task
async Task<object> DoTaskAsync(string id, CancellationToken token, int delay)
{
Console.WriteLine("Task: " + id);
await Task.Delay(delay, token); // do some work
return id;
}
// DoTaskSequenceAsync depends on Task1, Task2, Task3
async Task<object> DoTaskSequenceAsync(string id, CancellationToken token)
{
Console.WriteLine("Task: " + id);
await DoTaskAsync(id + "." + "Task1", token, 1000);
await DoTaskAsync(id + "." + "Task2", token, 2000);
await DoTaskAsync(id + "." + "Task3", token, 3000);
// do more
return id;
}
// a bad task which throws
async Task<object> BadTaskAsync(string id, CancellationToken token, int delay)
{
Console.WriteLine("Task: " + id);
await Task.Delay(delay, token);
throw new ApplicationException(id);
}
// wraps a task and requests the cancellation if the task has failed
async Task<T> WrapAsync<T>(CancellationTokenSource cts,
Func<CancellationToken, Task<T>> taskFactory)
{
try
{
return await taskFactory(cts.Token);
}
catch
{
if (!cts.IsCancellationRequested)
{
cts.Cancel(); // cancel the others
}
throw; // rethrow
}
}
// run all tasks
public async Task DoWorkAsync(CancellationToken outsideCt)
{
var tasks = new List<Task<object>>();
var cts = new CancellationTokenSource();
ExceptionDispatchInfo capturedException = null;
try
{
using (outsideCt.Register(() => cts.Cancel()))
{
// these tasks run in parallel
tasks.Add(WrapAsync(cts, (token) => DoTaskAsync("Task1", token, 500)));
tasks.Add(WrapAsync(cts, (token) => DoTaskSequenceAsync("Sequence1", token)));
tasks.Add(WrapAsync(cts, (token) => DoTaskAsync("Task2", token, 1000)));
tasks.Add(WrapAsync(cts, (token) => BadTaskAsync("BadTask", token, 1200)));
tasks.Add(WrapAsync(cts, (token) => DoTaskSequenceAsync("Sequence2", token)));
tasks.Add(WrapAsync(cts, (token) => DoTaskAsync("Task3", token, 1500)));
await Task.WhenAll(tasks.ToArray());
}
}
catch (Exception e)
{
capturedException = ExceptionDispatchInfo.Capture(e);
}
if (outsideCt.IsCancellationRequested)
{
Console.WriteLine("Cancelled from outside.");
return;
}
if (cts.IsCancellationRequested || capturedException != null)
{
if (cts.IsCancellationRequested)
{
Console.WriteLine("Cancelled by a failed task.");
// find the failed task in tasks or via capturedException
}
if (capturedException != null && capturedException.SourceException != null)
{
Console.WriteLine("Source exception: " + capturedException.SourceException.ToString());
// could rethrow the original exception:
// capturedException.Throw();
}
}
Console.WriteLine("Results:");
tasks.ForEach((task) =>
Console.WriteLine(String.Format("Status: {0}, result: {1}",
task.Status.ToString(),
task.Status == TaskStatus.RanToCompletion? task.Result.ToString(): String.Empty)));
}
}
static void Main(string[] args)
{
var cts = new CancellationTokenSource(10000);
new Worker().DoWorkAsync(cts.Token).Wait();
Console.WriteLine("Done.");
Console.ReadLine();
}
}
}