roslynator
roslynator copied to clipboard
Detect implicit conversion of async delegates
To complement CS4014 that detects async calls not await'ed, it could be nice to have an analyzer to detect implicit conversions from async delegates to sync ones.
async Task FooAsync<T>(Func<T> func)
{
await Task.Delay(0);
func(); // the async delegate is not await'ed!
}
Task BarAsync(Func<Task> func)
{
return FooAsync(func); // implicit conversion to Func<T>
}
It's not always easily detectable using Intellisense

How the code fix would look like?
My thought was mostly as an analyzer that could detect the problematic usage. So an analyzer without an automatic code fix.
A manual code fix could of course be
async Task FooAsync<T>(Func<Task> func) // Func<Task> overload
{
await Task.Delay(0);
await func(); // await the async delegate
}
To summarize it, you propose to change:
func();
to
await func();
where func is of type Func<TAwaitable>
is this correct?
Not exactly, let me try with a better example.
I have a utility function Retry.UpToThreeTimes that retries a Func<T> up to three times if it throws exceptions.
static class Retry
{
public static void UpToThreeTimes<T>(Func<T> func)
{
Console.WriteLine("start retry");
for (int i = 0; i < 3; i++)
{
try
{
func();
}
catch
{
Console.WriteLine("try again");
continue;
}
Console.WriteLine("non-failure");
return;
}
Console.WriteLine("tried three times without luck");
}
}
It's used in two of my programs.
class Program
{
public static void Main()
{
Func<int> func = () => throw new Exception();
Retry.UpToThreeTimes(func);
}
}
class Program2
{
public static void Main()
{
Func<int> func = () => throw new Exception();
Retry.UpToThreeTimes(func);
}
}
The output of both programs is
start retry
try again
try again
try again
tried three times without luck
Now I want to refactor Program2 to use some async method, but I'm missing out that Retry.UpToThreeTimes is not Task aware.
class Program2
{
public static async Task Main()
{
Func<Task<int>> func = async () =>
{
await Task.Delay(0);
throw new Exception();
};
Retry.UpToThreeTimes(func); // Func<Task<int>> still matches the `Func<T>` signature
}
}
As Retry.UpToThreeTimes is not Task aware it cannot await func() and is not testing whether the running task throws an exception, but whether creating the task throws an exception.
So instead of retrying the Func<Task<int>> three times, the output is now simply:
start retry
non-failure
To summarize:
- An analyzer that catches
Func<Task<TResult>>being cast toFunc<TNonAwaitable> - I'm not sure about an automatic codefix as
ProgramusesRetry.UpToThreeTimescorrectly.