【问题标题】:Can we have an async Action?我们可以有一个异步操作吗?
【发布时间】:2022-02-04 05:38:24
【问题描述】:

我制作了一个运行良好的事件系统,它允许我通过传递事件类型和处理程序来注册事件处理程序,这是一个动作,例如

EventManager.RegisterEventHandler<Events.NewCompleteSample>(HandleCompleteSample);

打电话

public static void RegisterEventHandler<T>(Action<T> handlerAction)

稍后引发事件时,我调用已注册的操作。

我的问题是我随机发生了静默崩溃,我现在意识到这可能是因为我调用的操作可能(通常是)异步方法,而我知道未捕获到非等待异步方法中的异常为时已晚任何地方。

解决方案似乎是等待处理程序

await handler.Invoke(arguments)

但由于我不知道如何指定 Actions 是异步方法,所以这不起作用。

有没有办法将 Action 定义为异步,以便以后调用它时可以等待它,或者是否有更聪明的解决方案来解决这个问题?

【问题讨论】:

  • 是的,异步操作是Func&lt;Task&gt;
  • “异步函数”是真正的“可等待函数”的一个非常常见的术语。如果您以那样的方式考虑它,它会更有意义。 async 关键字与编译器为您创建的异步状态机有关,但您不需要它来等待某些东西。
  • 感谢@Fabio,这很快并且似乎在测试中有效。将进行实验。
  • 它是等待的,因为它遵循一个模式。 TaskTask&lt;T&gt;,以及 ValueTaskValueTask&lt;T&gt; 都遵循该模式,使其可等待,因此您可以使用 await 关键字。并非所有可等待函数都具有 async 关键字,因为它们不需要编译器将方法重写为状态机。有时,他们只是return Task.Completed;。这不需要状态机,但它仍然可以等待。
  • 可能相关问题:How do I await events in C#?

标签: c# async-await


【解决方案1】:

我的问题是我随机发生了静默崩溃,我现在意识到这可能是因为我调用的操作可能(通常是)异步方法,而我知道未捕获到非等待异步方法中的异常为时已晚任何地方。

有点。如果从未观察到任务,则从 normal 异步方法(即返回 TaskTask&lt;T&gt; 的方法)抛出的异常将被忽略。 Exceptions thrown from async void methods do crash the process;这是一个深思熟虑的设计决定。

有没有办法将Action定义为异步,

当然;只需将委托映射到函数签名,使它们异步,然后再映射回来。这会给你asynchronous delegate types(如我的博客所述)。

在这种情况下,Action 映射到类似void Handle() 的方法;其异步版本是Task HandleAsync(),它映射到Func&lt;Task&gt; 类型的委托。

所以你可以在以后调用它时等待它,

嗯,有点。当然,await handler.Invoke(); 会编译,但事件(和委托)可以包含任意数量的处理程序,并且Invoke 只返回 last 结果,因此 await 等待从返回的 Task最后一个处理程序。如果有多个处理程序,则所有其他 Tasks 都将被丢弃(并且它们的异常会被默默吞下)。

或者是否有更聪明的解决方案来解决这个问题?

asynchronous events 有一些解决方案(链接到我的博客)。

首先,我会考虑调整设计。事件非常适合观察者设计模式,但需要await 处理程序通常意味着事件被滥用来实现不同的设计模式,通常是命令或策略设计模式。考虑用不同的东西(例如界面)完全替换事件。

如果这不可行,那么我会考虑通过调查 Reactive Observables 来使用不同的事件表示。 Observables 是真正的事件应该。但是 observables 可能会变得相当复杂。

另一种方法是使用延期。本质上,您保留async void 方法,但处理程序需要通过在using 中获取延迟来“加入”异步处理。然后调用代码等待所有延迟被释放。

最后一种方法是使用Func&lt;Task&gt;,通过GetInvocationListawait 获得所有任务,一次一个或使用Task.WhenAll

【讨论】:

  • 感谢您的冗长回答。我现在不容易从根本上改变设计。因为通过事件管理器,事件引发和处理是 100% 解耦的,这使得很多事情变得超级简单。等待处理程序 Func 的问题不清楚。如果该处理程序 Func 是异步的并且等待另一个可能等待另一个异步方法的异步方法,那么等待顶部 Func 是否一定会捕获整个链中的异常?
  • @JoeTaicoon:是的,只要链中没有async void 方法。
【解决方案2】:

您可以使用 Func 创建一个“等待操作”,它接收对象类型的参数并且没有返回类型。 https://docs.microsoft.com/en-us/dotnet/api/system.func-2?f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(System.Func%602);k(SolutionItemsProject);k(DevLang-csharp)%26rd%3Dtrue&view=net-6.0

    private async Task ExecuteAsync(Func<object, Task> action, object args)
    {
       await action.Invoke(args);
    }

【讨论】:

  • "并且没有返回类型" -- Func&lt;object, Task&gt; 确实有返回类型。返回类型是Task。您可能的意思是这个Task 是一个非通用任务。它没有 Result 属性。
  • 好点,但最好保留原文,因为它使用与问题相同的语言
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-18
  • 2019-06-09
  • 1970-01-01
  • 2020-11-27
  • 1970-01-01
  • 2017-01-04
  • 1970-01-01
相关资源
最近更新 更多