我在数组中有一系列任务。如果任务为“好”,则返回一个字符串。如果它是“坏”:它返回null。
我希望能够并行运行所有任务,一旦第一个返回“好”,然后取消其他任务并获得“好”结果。
我现在正在这样做,但问题是需要运行所有任务,然后我遍历它们寻找第一个好结果。
List<Task<string>> tasks = new List<Task<string>>();
Task.WaitAll(tasks.ToArray());
我希望能够并行运行所有任务,一旦第一个返回“好”,然后取消其他任务并获得“好”结果。
这是误解,因为Cancellation in TPL is co-operative,所以一旦任务启动,就没有办法取消它。如果请求取消,CancellationToken
可以在Task启动之前工作或稍后抛出异常,这意味着启动并采取必要的操作,例如从逻辑中抛出自定义异常
检查以下query,列出了许多有趣的答案,但没有一个取消。以下也是一个可能的选择:
public static class TaskExtension<T>
{
public static async Task<T> FirstSuccess(IEnumerable<Task<T>> tasks, T goodResult)
{
// Create a List<Task<T>>
var taskList = new List<Task<T>>(tasks);
// Placeholder for the First Completed Task
Task<T> firstCompleted = default(Task<T>);
// Looping till the Tasks are available in the List
while (taskList.Count > 0)
{
// Fetch first completed Task
var currentCompleted = await Task.WhenAny(taskList);
// Compare Condition
if (currentCompleted.Status == TaskStatus.RanToCompletion
&& currentCompleted.Result.Equals(goodResult))
{
// Assign Task and Clear List
firstCompleted = currentCompleted;
break;
}
else
// Remove the Current Task
taskList.Remove(currentCompleted);
}
return (firstCompleted != default(Task<T>)) ? firstCompleted.Result : default(T);
}
}
var t1 = new Task<string>(()=>"bad");
var t2 = new Task<string>(()=>"bad");
var t3 = new Task<string>(()=>"good");
var t4 = new Task<string>(()=>"good");
var taskArray = new []{t1,t2,t3,t4};
foreach(var tt in taskArray)
tt.Start();
var finalTask = TaskExtension<string>.FirstSuccess(taskArray,"good");
Console.WriteLine(finalTask.Result);
您甚至可以返回Task<Task<T>>
而不是Task<T>
进行必要的逻辑处理
您可以使用以下示例获得所需的结果。
List<Task<string>> tasks = new List<Task<string>>();
// ***Use ToList to execute the query and start the tasks.
List<Task<string>> goodBadTasks = tasks.ToList();
// ***Add a loop to process the tasks one at a time until none remain.
while (goodBadTasks.Count > 0)
{
// Identify the first task that completes.
Task<string> firstFinishedTask = await Task.WhenAny(goodBadTasks);
// ***Remove the selected task from the list so that you don't
// process it more than once.
goodBadTasks.Remove(firstFinishedTask);
// Await the completed task.
string firstFinishedTaskResult = await firstFinishedTask;
if(firstFinishedTaskResult.Equals("good")
// do something
}
编辑:如果要终止所有任务,可以使用CancellationToken。
有关更多详细信息,请阅读docs。
我正在调查Task.WhenAny()
,这将触发第一个“完成”的任务。不幸的是,在这个意义上完成的任务基本上是任何事情......甚至一个例外被认为是“完成的”。据我所知,没有其他方法可以检查你称之为“好”的价值。
虽然我不相信你的问题有一个令人满意的答案我认为可能有一个替代解决方案来解决你的问题。考虑使用Parallel.ForEach
。
Parallel.ForEach(tasks, (task, state) =>
{
if (task.Result != null)
state.Stop();
});
当state.Stop()
找到非null结果时,它将停止执行Parallel循环。
除了能够在找到“好”值时停止执行,它在许多(但不是全部)场景下表现更好。
使用Task.WhenAny
它返回完成的任务。检查它是否为空。如果是,请从列表中删除它并再次调用Task.WhenAny。
如果它是好的,取消列表中的所有任务(他们应该都有一个CancellationTokenSource.Token
。
编辑:
所有任务应使用相同的CancellationTokenSource.Token
。那你只需要取消一次。以下是一些澄清的代码:
private async void button1_Click(object sender, EventArgs e)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
List<Task<string>> tasks = new List<Task<string>>();
tasks.Add(Task.Run<string>(() => // run your tasks
{
while (true)
{
if (cancellationTokenSource.Token.IsCancellationRequested)
{
return null;
}
return "Result"; //string or null
}
}));
while (tasks.Count > 0)
{
Task<string> resultTask = await Task.WhenAny(tasks);
string result = await resultTask;
if (result == null)
{
tasks.Remove(resultTask);
}
else
{
// success
cancellationTokenSource.Cancel(); // will cancel all tasks
}
}
}