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

前端 未结 4 1090
死守一世寂寞
死守一世寂寞 2020-12-13 17:38

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.

相关标签:
4条回答
  • 2020-12-13 18:04

    Bill Wagner does a great job of explaining this question here - https://www.thebillwagner.com/blog/Item/2016-05-18-DoasynclambdasreturnTasks

    0 讨论(0)
  • 2020-12-13 18:14

    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.

    The general rule, even without async, is that a delegate with a return type is a better match than a delegate without a return type. Another example of this is:

    static void Foo(Action a) { }
    static void Foo(Func<int> f) { }
    static void Bar()
    {
      Foo(() => { throw new Exception(); });
    }
    

    This is unambiguous and calls the second overload of Foo.

    Also, is there a way to explicitly declare which overload I wish to use?

    A nice way to make this clear is to specify the parameter name. The parameter names for the Action and Func<Task> overloads are different.

    Task.Run(action: async () => {
      await Task.Delay(1000);
    });
    Task.Run(function: async () => {
      await Task.Delay(1000);
    });
    
    0 讨论(0)
  • 2020-12-13 18:15

    There is a nice Roslyn analyzer from Microsoft team.

    This analyzer helps prevent inadvertent creation of async void delegates.

    https://github.com/Microsoft/vs-threading/blob/master/doc/analyzers/VSTHRD101.md

    0 讨论(0)
  • 2020-12-13 18:21

    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);
    }));
    
    0 讨论(0)
提交回复
热议问题