问题
I recently asked a question about the possibility of cleaning up some code, the code was meant to wait for each Task
in a List<Task<T>>
to complete but cancelling all Task
if one returned some falsey value.
A user named Servy quickly produced a method which eloquently orders a List<Task<T>>
by their completion time. After reading over the answer a bit I think/thought I understand/stood the method. I then went to use the method but quickly realized an issue. I need to be able to recognize the Task
s as they are completing. But the Order
method Servy suggested provides no means to do this (because I cannot compare the task continuations returned by Order
to my List<Task<>>
I supplied it originally).
So I went to change the method to return a Tuple<T, int>
where the int
represents the original index of the Task
in the provided arguments.
public static IEnumerable<Task<Tuple<T, int>>> OrderByCompletion<T>(IEnumerable<Task<T>> tasks)
{
var taskList = tasks.ToList();
var taskSources = new BlockingCollection<TaskCompletionSource<Tuple<T, int>>>();
var taskSourceList = new List<TaskCompletionSource<Tuple<T, int>>>(taskList.Count);
for (int i = 0; i < taskList.Count; i++)
{
var task = taskList[i];
var newSource = new TaskCompletionSource<Tuple<T, int>>();
taskSources.Add(newSource);
taskSourceList.Add(newSource);
task.ContinueWith(t =>
{
var source = taskSources.Take();
if (t.IsCanceled)
source.TrySetCanceled();
else if (t.IsFaulted)
source.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCompleted)
source.TrySetResult(new Tuple<T, int>(t.Result, i));
}, CancellationToken.None, TaskContinuationOptions.PreferFairness, TaskScheduler.Default);
}
return taskSourceList.Select(tcs => tcs.Task);
}
// Usage
foreach(var task in myTaskList.OrderByCompletion())
Tuple<Boolean, int> result = await task;
The issue I am facing is that index inside of the Tuple
returned seems to always be equal to the Count
of the original List<Task<>>
passed to OrderByCompletion
no matter the order of the task returned.
I assume that as a result of this issue I do not fully understand how this method worked in the first place, though it seems to be that it should work just fine.
Can anyone explain the issue and offer a solution?
回答1:
This happens because you are using i
variable inside an Action<>
.
But the code of this action is not executed when your create the Action
, but when the task is completed, then the variable i
has value taskList.Count
(when for
loop finished).
You can fix your problem simply adding an extra variable to the for
:
for (int i = 0; i < taskList.Count; i++)
{
var task = taskList[i];
var newSource = new TaskCompletionSource<Tuple<T, int>>();
taskSources.Add(newSource);
taskSourceList.Add(newSource);
int index = i; // <- add this variable.
task.ContinueWith(t =>
{
var source = taskSources.Take();
if (t.IsCanceled)
source.TrySetCanceled();
else if (t.IsFaulted)
source.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCompleted)
source.TrySetResult(new Tuple<T, int>(t.Result, index));
}, CancellationToken.None, TaskContinuationOptions.PreferFairness, TaskScheduler.Default);
}
You can read this question/answers for more details.
来源:https://stackoverflow.com/questions/36799825/ordering-tasks-by-completion-time-while-keeping-track-of-their-index-in-an-argum