Getting reference to original Task after ordering Tasks by completion?

南楼画角 提交于 2019-12-10 16:46:03

问题


I asked a question a while ago about a method that orders a List<Task<T>>> by their completion that also returns an int representing the index of the completed Task in the original List<Task<T>> given.

I have the inkling that I might not need to return this int to determine which specific Task has completed and that I can interrogate the returned Task for this information.


As a side note, I have since altered the method to order a List<Task>. I originally used Task<T>, which returned a bool to represent if the Task<T> was successful in its job or not. I now simply throw a subclass of Exception which provides more information about how and why a Task failed.

My idea for this question came from the issue that when a Task<int> from this method throws an Exception I have no way to determine which specific Task threw the Exception because I cannot interrogate the Task<int>.Result for the Task's original index.

So again, if I can interrogate the Task<int> (now simply just Task) that is returned for the Task it refers to from the original list, I can simply compare the references.


Here is the method as it now exists (Credit to Servy for the original code for this method. Also possibly this blog post from Jon Skeet)

public static IEnumerable<Task<int>> OrderByCompletion(IEnumerable<Task> tasks)
{
    var taskList = tasks.ToList();
    var taskSources = new BlockingCollection<TaskCompletionSource<int>>();
    var taskSourceList = new List<TaskCompletionSource<int>>(taskList.Count);

    for (int i = 0; i < taskList.Count; i++)
    {
        var task = taskList[i];
        var newSource = new TaskCompletionSource<int>();
        taskSources.Add(newSource);
        taskSourceList.Add(newSource);

        int index = i;

        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(index);
        }, CancellationToken.None, TaskContinuationOptions.PreferFairness, TaskScheduler.Default);
    }

    return taskSourceList.Select(tcs => tcs.Task);
}

My first issue is that this method uses TaskCompletionSource<T> to track the TResult of the Task from the parameter list. Seeing as the List<Task>> now returns no value and uses Exceptions this is useless, though its usage is unavoidable because there is no non generically parameterized TaskCompletionSource<T> But this is not really a problem because I can just return some trash value.

So, to the question itself, can I interrogate the Task<(unused return value)> to get a reference to the Task it tracks?


From what I can tell the TaskCompletionSource<T> has no information about the Task it is tracking. It simply "looks like" the original Task.

Is the only option to subclass TaskCompletionSource<T> to add one property that refers to the Task it tracked, set the value of the property in the method, and then interrogate that property for the reference?

public class TaskHoldingTaskCompletionSource<T> : TaskCompletionSource<T>
{
    public Task OriginalTask { get; set; }
}

回答1:


Just look at the way WhenAny solves this problem for its analogous operation. Have the method return a Task whose result is the Task that it logically represents the completion of, rather than computing the same logical value as that task. This forces the caller to unwrap that task if they just care about the task's result (or error state), but if you don't always want it unwrapped, you can just return the whole Task. It actually simplifies the code to not have the Order method unwrap the task for you.

public static IEnumerable<Task<Task>> Order2(this IEnumerable<Task> tasks)
{
    var taskList = tasks.ToList();

    var taskSources = new BlockingCollection<TaskCompletionSource<Task>>();

    var taskSourceList = new List<TaskCompletionSource<Task>>(taskList.Count);
    foreach (var task in taskList)
    {
        var newSource = new TaskCompletionSource<Task>();
        taskSources.Add(newSource);
        taskSourceList.Add(newSource);

        task.ContinueWith(t => taskSources.Take().TrySetResult(t),
            CancellationToken.None, TaskContinuationOptions.PreferFairness, TaskScheduler.Default);
    }

    return taskSourceList.Select(tcs => tcs.Task);
}


来源:https://stackoverflow.com/questions/40030522/getting-reference-to-original-task-after-ordering-tasks-by-completion

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!