【问题标题】:Explicitly use a Func<Task> for asynchronous lambda function when Action overload is available当 Action 重载可用时,为异步 lambda 函数显式使用 Func<Task>
【发布时间】:2013-12-22 03:32:00
【问题描述】:

阅读 this 博客文章,了解 C#5 的 async/await 的一些陷阱。它在 Gotcha #4 中提到了一些我以前没有想到的非常深刻的东西。

简而言之,它涵盖了一个方法具有两个重载的场景,一个采用Action,另一个采用Func&lt;Task&gt;(例如Task.Run)。这个问题的根源在于 async void 方法应该只用于事件处理程序的论点,然后帖子继续描绘以下场景 - 当像下面这样的 lambda 函数可以编译为两者时,编译器会推断什么Func&lt;Task&gt;Action:

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

因为Task.RunTask.Run(Func&lt;Task&gt;)Task.Run(Action) 的签名,所以异步匿名函数编译成什么类型​​? async void 还是 Func&lt;Task&gt;?我的直觉是它会编译成async void,纯粹是因为它是非泛型类型,但是C#编译器可能很聪明,并且会优先考虑Func&lt;Task&gt;类型。

另外,有没有办法明确声明我希望使用哪个重载?我知道我可以创建一个Func&lt;Task&gt; 的新实例并在那里传递异步 lambda 函数,但它仍会编译为async void,然后将其传递给Func&lt;Task&gt; 的构造函数。确保将其编译为Func&lt;Task&gt; 的理想方法是什么?

【问题讨论】:

  • 微软确实改变了编译器,让它选择Func&lt;Task&gt;而不是Action,其他条件相同。 AFAIK,Eric Lippert 还没有在他的博客上解决这个问题...... :)

标签: c# async-await c#-5.0 lambda


【解决方案1】:

因为Task.RunTask.Run(Func&lt;Task&gt;)Task.Run(Action) 的签名,所以异步匿名函数编译成什么类型​​? async void 还是 Func&lt;Task&gt;?我的直觉是它会编译成async void,纯粹是因为它是一个非泛型类型,但是C#编译器可能很聪明,并且会优先考虑Func&lt;Task&gt;类型。

一般规则,即使没有async,具有返回类型的委托比没有返回类型的委托更匹配。另一个例子是:

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

这是明确的,并调用Foo 的第二个重载。

另外,有没有办法明确声明我希望使用哪个重载?

明确这一点的一个好方法是指定参数名称。 ActionFunc&lt;Task&gt; 重载的参数名称不同。

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

【讨论】:

  • 使用参数名来选择重载真的很酷。否则,会将整个 lambda 转换为所需的委托类型,但这看起来很难看。在您的第一个示例中,参数 f 的名称有点令人困惑(尽管合法),因为该方法本身也称为 f
  • 太棒了!在 .NET Framework 中使用不同的参数名称是否标准?
  • @jduncanator 我不确定如何阅读。参数名称被认为是 API 的一部分,对参数名称的更改被认为是重大更改,因此它是“标准的”,因为您可以相信 MS 不会在没有充分理由的情况下突然更改它。这能回答你的问题吗?
【解决方案2】:

我刚刚检查了它是否默认编译为Task.Run(Func&lt;Task&gt;),对此我没有很好的解释。

这是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

您可以使用 Visual Studio 类型推断轻松检查这一点,如果您将鼠标放在方法上,它会显示将编译的方法,或者只需单击方法按 F12 您可以看到元数据会告诉你编译器推断的类型是什么。

另外,有没有办法明确声明我想要的重载 采用? 是的,明确指定委托。

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

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

【讨论】:

  • 如果您阅读下一句,这只是“排序”缓解了问题...内部 async () =&gt; lambda 函数仍编译为 async void 委托。
  • 我的编辑有意义吗?或者我错过了什么..?
  • 是的!您的编辑非常有意义!我在方法参数中是Ctrl-Shift-Spaceing,它显示了Action 重载签名,所以我认为它正在编译为async void。原来不是!编译器会自动为异步 lambda 选择 Func 作为优先于 Action!谢谢!
【解决方案3】:

Microsoft 团队有一个不错的 Roslyn 分析器。

此分析器有助于防止无意中创建异步 void 委托。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 2022-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多