4

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; }
}
Community
  • 1
  • 1
KDecker
  • 6,928
  • 8
  • 40
  • 81

1 Answers1

3

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);
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • I had had the idea that instead of using the `` as a trash value to return the `Task` as you have here. But the issue I saw is that if I try to interrogate the `Task.Result` and the original `Task` threw an `Exception` I wouldn't be able to get the reference to the `Task` just the `Exception` it threw. // Though I might need to think this over before I really commit to thinking that. // Actually I think I might not fully understand `TaskCompletionSource` yet.. – KDecker Oct 13 '16 at 20:58
  • @KDecker In this code we *never* set any `Task` returned by `Order2` to faulted. If the first `Task` to complete faults, `Order2` will return a `Task` *that successfully ran to completion* that has a `Result` holding onto the faulted `Task`. – Servy Oct 13 '16 at 20:59
  • Ohhhh I see now (I think). Wow, yet another intellectually aesthetic piece of code there. Thanks again Servy! – KDecker Oct 13 '16 at 21:03