Michael Ray Lovett
Michael Ray Lovett

Reputation: 7070

C# How to have a generic method do awaiting?

Okay, bad title, but I couldn't think of a better name..My question isn't probably even specific to async/await, but my question is coming up during async handling, so I'm going to pose it that way:

I have several methods which create lists of tasks and then do an 'await Task.WhenAll(list of tasks)". The specific kind of tasks being awaited on in these methods varies. For example, some methods are awaiting a list of Task<String>, while others are awaiting a list of Task<foo>.

What I'm finding is that I'm needing to do some non-trivial try/catch processing around the Task.WhenAll() in each of these methods, and that code is always the same. I'd like to move that code to a common method, and then pass in the list of tasks and have that common method issue then WhenAll, wrapped up in the try/finally.

But the problem I'm running in to is that each of the methods calling this method will be passing in lists of different Task types, and this causes a compiler complain when I declare the parameter to my common method as just Task:

methodA:
    List<Task<String>> myTaskList = ...
    ExecuteTasks(myTaskList);

methodB:
    List<Task<Foo>> myTaskList = ...
    ExecuteTasks(myTaskList);

 async Task ExecuteTasks(List<Task> taskList) {
    try {
        await Task.WhenAll(taskList)
        }
    catch {
        ..common catch handling goes here. This handling isn't really sensitive to the
        ..type of the Tasks, we just need to examine it's Status and Exception properties..
        }
    }

In the above, methodA and methodB each have their own kinds of task lists that need to be passed in to ExecuteTasks, but the question is how to define the list of tasks to ExecuteTasks so that the compiler doesn't complain about type mismatches? In a non-generic world, I would probably define the parameter to ExecuteTasks a super-class of the the types of list of methodA and methodB so the compiler could "upcast" them, but this approach doesn't seem to work here.. (I tried defining ExecuteTasks as taking a Task<Object> but that didn't solve the type mismatch problem)

Upvotes: 4

Views: 1244

Answers (3)

trydis
trydis

Reputation: 3925

var intTask1 = Task.Run(() => 1);
var intTask2 = Task.Run(() => 2);
var intTasks = new List<Task<int>> { intTask1, intTask2 };

var intExecutor = new TaskExecutor<int>();
await intExecutor.ExecuteTasks(intTasks);

var stringTask1 = Task.Run(() => "foo");
var stringTask2 = Task.Run(() => "bar");
var stringTasks = new List<Task<string>> { stringTask1, stringTask2 };

var stringExecutor = new TaskExecutor<string>();
await stringExecutor.ExecuteTasks(stringTasks);

..................................

class TaskExecutor<T>
{
    public async Task ExecuteTasks(IEnumerable<Task<T>> tasks)
    {
        try
        {
            await Task.WhenAll(tasks);
        }
        catch (Exception ex)
        {
            // Handle exception
        }
    }
}

Upvotes: 1

Chris Sinclair
Chris Sinclair

Reputation: 23208

Try typing your ExecuteTasks against an IEnumerable<Task> instead:

async Task ExecuteTasks(IEnumerable<Task> taskList) {

As @Hamish Smith pointed out, this is an issue of covariance.

List<Task<String>> myTaskList = ...

ExecuteTasks(myTaskList);

async Task ExecuteTasks(IEnumerable<Task> taskList) {
    try {
        await Task.WhenAll(taskList)
        }
    catch {
        //..common catch handling goes here. This handling isn't really sensitive to the
        //..type of the Tasks, we just need to examine it's Status and Exception properties..
        }
}

If it were still typed against List<Task>, then you could do something silly like this:

List<Task<String>> myTaskList = ...

ExecuteTasks(myTaskList);

async Task ExecuteTasks(List<Task> taskList) {
        taskList.Add(new Task<int>()) // bad stuff
}

Upvotes: 2

Hamish Smith
Hamish Smith

Reputation: 8181

While I should really point you at Eric Lippert's series on contra- and co- variance and how generics are seen by the compiler (http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx)...
I do wonder if a generic method would work here?

  async Task ExecuteTasks<T>(List<Task<T>> taskList) 
  {
     try 
     {
        await Task.WhenAll(taskList);
     }
     catch 
     {
        //..common catch handling goes here. This handling isn't really sensitive to the
        //..type of the Tasks, we just need to examine it's Status and Exception properties..
     }
  }

Upvotes: 0

Related Questions