Func<T, Task>和匿名异步等待Action<T>之间的区别

4
我正在开发ASP.NET Core Blazor,并且有时会查看GitHub以学习一些东西。这个问题与Blazor没有直接关系,但我看到很多Blazor开发人员正在做我将要解释的事情。
我注意到一些组件接受一个Action<T>作为参数,而其他组件则接受一个Func<T, Task>
/* example */

public class MyComponentA : ComponentBase 
{
    [Parameter] public Action<T> OnClick {get; set;}

    //... other methods
}

public class MyComponentB : ComponentBase 
{
    [Parameter] public Func<T, Task> OnClickAsync {get; set;}

    //... other methods
}


我理解的是您应该将 Action<T> 绑定到一个无异步/等待方法,而将 Func<T, Task> 绑定到一个异步方法中。到目前为止还不错。

然而,我注意到有人会将异步/等待匿名函数作为 Action<T> 传递,例如 OnClick=@(async (item) => await Foo(item))。有时候,我也会将匿名的异步/等待函数作为 Action 传递,它可以正常工作。

  • 这样做正确吗?

  • 会有任何副作用吗?

  • 调用一个匿名异步/等待函数的Action<T>,是一次异步调用吗?


Action 是一个没有返回值的委托; Func 是一个有返回值的委托。使用哪个委托与特定方法是否异步执行无关。 - Kenneth K.
那么为什么有些开发人员会说类似于“现在,使用Func<T,Task>,我们的组件接受异步方法。UI线程不会被我们的组件阻塞。”根据您的回答,似乎这句话没有任何意义。看起来Func<T,Task>是一种更好的方式来接受async / await方法,而无需使用async / await匿名方法。 - Leonardo Lurci
2个回答

8

Action<T>不允许返回值,只有返回可等待类型的方法(通常是TaskTask<T>)可以被等待。

因此,如果您将异步方法作为Action<T>传递,它将始终被视为fire and forget。

如果使用Func并以Task作为返回值,则可能会等待,这取决于该方法的实现方式。


1
Fire and Forget仅适用于异步方法,因为它们在某个时刻放弃了线程的控制,如果不等待它,那么它后面的代码将不会继续执行。然而,对于同步方法,整个代码将在线程继续执行其后面的任何代码之前运行完毕。 - juharr
@juharr 是的,也许措辞不太好;我会更新的。 - Johnathan Barclay
在Blazor上下文中,等待Func<T, Task>非常有用,因为它允许开发人员使用StateHasChanged()触发UI更新以反映函数的效果。 - Rich Bryant
@Rich,我可以请你给我展示一个例子吗?谢谢! - Leonardo Lurci
我手头没有,但是想一想——你不知道“fire and forget”调用的结果何时完成,也不太关心。因为你可以等待从Func返回的Task(尽管说实话,C#需要一个“Unit”类型),你知道它已经完成并且可以安全地调用StateHasChanged。 - Rich Bryant
“Fire and forget”适用于Task。在async void方法的情况下,没有Task,因此没有什么可以被遗忘。差异在于异常的处理方式。故障的“fire-and-forget”Task会触发TaskScheduler.UnobservedTaskException事件,而没有其他后果。故障的async void会触发AppDomain.UnhandledException事件,然后进程终止。 - Theodor Zoulias

7
有时我把一个匿名的async/await函数当做Action传递,它也能工作。这段代码可以通过编译。实际上,编译器会将async lambda转换为async void方法,而async void方法是有问题的。这样做可能是不正确的,可能会产生副作用。
如果委托只是一个“通知”,那么它基本上就是一个事件,async void通常适用于事件。问题是:Blazor如何知道它的工作已经完成?Blazor确实有一个SynchronizationContext;然而,它的SynchronizationContext并不检测到async void处理程序。因此,Blazor可能会在它实际还有更多工作要做时决定它的工作已经完成。

调用匿名的async/await函数的Action,是异步调用吗?

是的,但它是async void,所以它看起来不是异步的。例如,委托调用者无法知道委托何时完成执行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接