How to make async method generic over Task type

My question is on the surface very similar to Using a generic type as a return type of an async method. However, he did not have a real world usage scenario and so all that could be stated is that you can't do that. I understand from Jon Skeet's answer to that question and from the blog post Why must async methods return Task? why it is not supported. The post discusses workarounds, but none of them apply to my use case.

My question is, what is the best way to work around the language limitation given what I am trying to do?

In production code, we had a retry method that takes a Func<Task> and returns Task. We then needed to be able to retry TaskT> functions as well. So we changed the retry method to (slightly simplified):

public static async T Retry<T>(Func<T> action) where T : Task
{
    for (var i = 0; ; i++)
    {
        try
        {
            return await action();
        }
        catch (Exception)
        {
            if (i >= 3)
                throw;
        }       
    }
}

The compiler of course reports the error "The return type of an async method must be void, Task or Task". Can I make Retry work with both Task and Task<T> without writing two copies of the method?

Jon Skeet
people
quotationmark

No, async just doesn't support that. The simplest option to fake it would be:

public static async Task<T> Retry<T>(Func<Task<T>> action)
{
    for (var i = 0; ; i++)
    {
        try
        {
            return await action();
        }
        catch (Exception)
        {
            if (i >= 3)
                throw;
        }       
    }
}

public static async Task Retry(Func<Task> action)
{
    Func<Task<int>> d = async () => { await action(); return 0; };
    await Retry(d);
}

That doesn't involve code duplication, but you do end up with an awful lot of layers of tasks on top of each other when you call the non-generic version. You could change the latter to:

public static Task Retry(Func<Task> action)
{
    Func<Task<int>> d = async () => { await action(); return 0; };
    return Retry(d);
}

Then it will actually return a Task<int>, but assuming your callers don't care, it does save one bit of redirection.

people

See more on this question at Stackoverflow