按完成时间排序任务,同时在参数列表中跟踪它们的索引?

Ordering Tasks by completion time while keeping track of their index in an argument list?

我最近 关于清理一些代码的可能性,代码本来是要等待 List<Task<T>> 中的每个 Task 完成但取消所有 Task 如果 return 编辑了一些错误的值。

一个名为 Servy 的用户雄辩地按完成时间订购了 List<Task<T>>。在稍微阅读了答案后,我 think/thought 我 understand/stood 方法。然后我去使用这个方法,但很快意识到一个问题。我需要能够在 Task 完成时识别它们。但是 Servy 建议的 Order 方法无法做到这一点(因为我无法将 Order 编辑的任务延续 return 与我最初提供的 List<Task<>> 进行比较。

所以我将方法更改为 return a Tuple<T, int> 其中 int 表示提供的参数中 Task 的原始索引。

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;

我面临的问题是 Tuple returned 中的索引似乎总是等于传递给 List<Task<>> 的原始 Count =25=] 不管任务的顺序如何 returned。

我假设由于这个问题,我一开始并不完全理解这个方法是如何工作的,尽管它似乎应该工作得很好。

谁能解释一下问题并提供解决方案?

发生这种情况是因为您在 Action<>.

中使用了 i 变量

但是这个动作的代码在你创建Action的时候并没有执行,但是当任务完成的时候,变量i的值是taskList.Count(当for 循环结束)。

您只需在 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);
}

您可以阅读这篇文章question/answers了解更多详情。