jduncanator jduncanator - 1 month ago 11
C# Question

Explicitly use a Func<Task> for asynchronous lambda function when Action overload is available

Reading over this blog post on some of the gotchas of C#5's async/await. It mentions in Gotcha #4 something that is quite profound and that I hadn't thought of before.

Briefly, it covers the scenario where you have a method that has two overloads, one that takes an

Action
and one that takes a
Func<Task>
(for example
Task.Run
). This issue is rooted in the argument that
async void
methods should only be used for event handlers, with the post then going on to portray the following scenario - What does the compiler infer when a lambda function like the following can be compiled to both a
Func<Task>
and an
Action
:

Task.Run(async () => {
await Task.Delay(1000);
});


Because
Task.Run
has signatures of both
Task.Run(Func<Task>)
and
Task.Run(Action)
, what type is the async anonymous function compiled to? An
async void
or a
Func<Task>
? My gut feeling says it will compile down to an
async void
purely because its a non-generic type however the C# Compiler might be smart and give
Func<Task>
types preference.

Also, is there a way to explicitly declare which overload I wish to use? I know I could just create a new instance of
Func<Task>
and pass in the async lambda function there but it would still compile down to a
async void
and then pass that into the constructor of the
Func<Task>
. What is the ideal way to make sure its compiled as a
Func<Task>
?

Answer

I just checked it gets compiled into Task.Run(Func<Task>) by default, I don't have good explanation for this.

Here is the relevant part of IL

IL_0001:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0006:  brtrue.s    IL_001B
IL_0008:  ldnull      
IL_0009:  ldftn       UserQuery.<Main>b__0
IL_000F:  newobj      System.Func<System.Threading.Tasks.Task>..ctor//<--Note here
IL_0014:  stsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0019:  br.s        IL_001B
IL_001B:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0020:  call        System.Threading.Tasks.Task.Run

you can check this easily using visual studio type inference, it will show you what method it will be compiled if you just place mouse over the method, or just click the method press F12 you can see the metadata which will tell you what was the type inferred by compiler.

Also, is there a way to explicitly declare which overload I wish to use? Yes, Specify the delegate explicitly.

Task.Run(new Action(async () =>
{
    await Task.Delay(1000);
}));

Task.Run(new Func<Task>(async () =>
{
    await Task.Delay(1000);
}));