【问题标题】:Implementing a Void interface signature with an Async method [closed]使用 Async 方法实现 Void 接口签名[关闭]
【发布时间】:2021-09-29 03:40:24
【问题描述】:

假设我正在开发一个使用具有以下签名的接口的 C# 库,例如:

public interface IMyInterface
{
    public void DoWork();
}

我有 2 个不同的类实现这个接口。

  • 只运行同步代码(所以void 返回类型就可以了)。
  • 需要等待异步调用(因此,理想情况下,我希望 DoWork 成为 async Task,而不是 void)。

非异步类不是问题,因为它确实只需要 void 返回类型:

public class NonAsyncClass : IMyInterface
{
    public void DoWork()
    {
        Console.WriteLine("This is a non-Async method");
    }
}

但我正在尝试考虑实现async 类的最佳方法。我正在考虑这样的事情:

public class AsyncClass : IMyInterface
{
    public void DoWork()
    {
        var t = Task.Run(async () => {
            await ExternalProcess();
        });

        t.Wait();
    }
}

但感觉这不是正确的做事方式。

当方法需要为async 时,对返回类型为void 的接口(假设您无法控制更改任何接口签名)进行编程的最佳方法是什么?或者反之亦然,当方法没有任何异步代码时,一个程序如何针对asyc Task 返回类型?

【问题讨论】:

  • 如果你有 Task 没有任何异步代码的返回类型,你可以在你的方法结束时返回Task.CompletedTask(或者如果你有Task<T>,你可以返回Task.FromResult(T result))。但是如果你有 void 方法并且你想在其中做一些异步的事情,你必须让你的代码同步,就像你用 Wait 做的那样
  • 我想知道的一件事:你真的想使用Task.Run 还是只想做类似ExternalProcess().Result; 的事情?
  • 您应该尽可能避免使用WaitGetAwaiter().GetResult()。问题是如果在调用者代码中使用任何类型的同步,它可能会导致死锁,例如Main-Thread 调用该方法,Task.Run 中的代码需要与 main 同步以更新 UI。 Main-Thread 可以t invoke that call because its 等待 Task 结束 -> 死锁。尝试将方法设计为Task DoWorkAsync,并在同步实现的情况下返回Taks.CompletedTask
  • @JohnBus​​tos 你不能做任何其他事情,如果你必须处理void 签名,但我同意@sebastian-schumann 的观点,它一点都不好。 :)
  • 如果您必须按原样使用接口,您有两个选择: 1. 调用者不需要知道并行发生的事情。只需使用Task.Run,无需等待任务结束。 2. 异步代码做了一些需要在方法返回之前完成的事情:你必须使用Wait 或者更好的GetAwaiter().GetResult() 等待。 BTW:如果你处理事件并且你有机会改变界面Stephen Cleary有一个很好的解决方案。

标签: c# asynchronous async-await


【解决方案1】:

当方法需要异步时,针对具有 void 返回类型的接口(假设您无法控制更改任何接口签名)进行编程的最佳方法是什么?

这与问“什么是阻塞异步代码的最佳方法”本质上是一样的,答案是一样的:没有最好的方法。有一个 variety of hacks - 正如我在 MSDN 文章中所描述的那样 - 但没有适用于所有场景的 hack。

blocking-threadpool-hack (Task.Run(async () => await ExternalProcess()).GetAwaiter().GetResult();) 可能适用于大多数场景;只有当ExternalProcess 必须访问与特定上下文相关的内容(例如,UI 元素或HttpContext.Current),或者它依赖于一次一个线程上下文提供的隐式同步时,它才会失败。因此,只要您的 ExternalProcess 不需要特定的上下文并且它是线程安全的,那么阻塞线程池 hack 就应该起作用。

反之亦然,当方法没有任何异步代码时,一个程序如何针对 asyc Task 返回类型?

这个很简单。异步兼容(例如,Task-returning)接口方法意味着实现可能是异步的,而不是它必须是异步的。同步实现可以使用Task.FromResultTask.FromException创建返回的Task。或者,您可以标记方法async 并忽略编译器警告,即the approach taken by my AsyncEx library

【讨论】:

  • 为什么在你的库中返回Task.CompletedTaskTask.FromResult 时忽略编译器警告?据我所见,ExecuteAsTask 方法将同步执行。 async 没有 awaitreturn Task.CompletedTask; 之间应该没有区别,还是我错过了一些运行时差异?我知道async 将创建一个状态机,但如果对func 的同步调用的运行时行为不会改变,则不需要这样做。我确定我错过了一些东西。请向我解释一下。
  • @SebastianSchumann:如果同步代码抛出异常,事情就会变得更加棘手。
  • 你有关于这方面的博客吗?我对此很感兴趣。
  • @SebastianSchumann:我没有,抱歉。但它变得越来越普遍,所以写一个可能是个好主意!
  • 哦,是的-拜托。
【解决方案2】:

如果 ExternalProcess 返回为 void,则不能对 void 使用 await。编译器会在尝试编译代码时告诉你。然后没有出现问题,因为接口。

如果 ExternalProcess 返回为“Task”数据类型,您的代码必须可以正常工作。

在您的示例代码中不清楚。您想将异步操作转换为同步吗?当用户调用 DoWork 方法时,程序将被阻塞,直到操作完成并继续执行下一行代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-17
    • 1970-01-01
    • 2015-10-19
    • 2011-12-13
    • 1970-01-01
    相关资源
    最近更新 更多