Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert a list of generic tasks of different types that are stored in a List<Task>, to a Task<List<object>>?

I am looking to create a Task<List> that when invoked with a set of methods executes in parallel, runs and returns the result in the same order of the tasks in an array. The Tasks can return different types. I tried below. Not sure if I am heading in right direction.

private async Task<IList<object>> RunTasks<T>(IList<Task> taskList)
{
    var allTasks = Task.WhenAll(taskList);
    var ret = new object[taskList.Count];
    await allTasks;

    for (int i=0;i<taskList.Count;i++)
    {
        ret[i] = taskList[i].IsFaulted ?
            default : ((Task<T>)taskList[i]).Result;
    }
    //otherPolicies.AppsPermissionsPolicy = teamsAppPermissionDocFromAAE
    //    .ToMTTeamsAppPermissionPolicy().ToMTPolicyDocument();
    //Wrap AAE TeamsApp doc response into other Policies
    return ret;
}

If Task1 & Task2 returns different types in taskList do we need T for RunTasks ? if so, What type do we pass in to Invoke RunTasks?. If we dont need it, then how do we convert the Tasks return type to its corresponding object in the for loop immediately after tasks completed before we return the object array with results?

like image 495
Programmerzzz Avatar asked Oct 14 '25 15:10

Programmerzzz


1 Answers

I think that converting a List<Task> to a List<Task<object>> cannot be done without reflection. Or without the dynamic keyword, like in the implementation below:

public static Task<object[]> WhenAllToObject(IEnumerable<Task> tasks)
{
    ArgumentNullException.ThrowIfNull(tasks);
    return Task.WhenAll(tasks.Select(async task =>
    {
        // First await the task, to ensure that it ran successfully.
        await task.ConfigureAwait(false);

        // Then try to get its result, if it's a generic Task<T>.
        try
        {
            return await (dynamic)task;
        }
        catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
        {
            throw new InvalidOperationException("Non-generic task found.");
        }
    }));
}

Usage example:

List<Task> heterogeneousListOfTasks = new()
{
    Task.FromResult(13),
    Task.FromResult("Hello"),
    Task.FromResult(true),
};
object[] results = await WhenAllToObject(heterogeneousListOfTasks);

Alternative: This one is inspired by IS4's ingenious type-matching trick. The advantage over the above implementation is that the validation of the tasks argument happens synchronously. That's because the pattern matching for the Task case is a non-async method (it elides async and await).

public static Task<object[]> WhenAllToObject(IEnumerable<Task> tasks)
{
    ArgumentNullException.ThrowIfNull(tasks);
    return Task.WhenAll(tasks.Select(task =>
    {
        if (task == null) throw new ArgumentException(
            $"The {nameof(tasks)} argument included a null value.", nameof(tasks));
        Task<object> taskOfObject = ToTaskOfObject((dynamic)task);
        if (taskOfObject == null) throw new ArgumentException(
            $"The {nameof(tasks)} argument included a non-generic Task.", nameof(tasks));
        return taskOfObject;
    }));
}
private static Task<object> ToTaskOfObject(Task task) // Not async
    => null;
private static async Task<object> ToTaskOfObject<T>(Task<T> task)
    => await task.ConfigureAwait(false);

Both implementations have similar behavior with the Task.WhenAll method, but not identical. The Task.WhenAll propagates all exceptions of all tasks. On the contrary the WhenAllToObject propagates only the first exception of each task.

like image 157
Theodor Zoulias Avatar answered Oct 17 '25 05:10

Theodor Zoulias



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!