returns任务接口的同步实现

Synchronous implementation of interface that returns Task

类似于 Implementing an interface that requires a Task return type in synchronous code 虽然我很好奇我是否应该忽略我的情况生成的编译器错误。

假设我有这样的界面:

public interface IAmAwesome {
    Task MakeAwesomeAsync();
}

在一些实现中,使用 asyncawait 异步完成带来了巨大的好处。这确实是界面试图允许的。

在其他情况下,也许很少见,只需要一个简单的同步方法就可以做出很棒的东西。所以让我们假设实现看起来像这样:

public class SimplyAwesome : IAmAwesome {
    public async Task MakeAwesomeAsync() {
        // Some really awesome stuff here...
    }
}

这有效,但编译器发出警告:

This method lacks 'await' operators and will run synchronously. Consider using the await operator to await non-blocking API calls, or 'await TaskEx.Run(...)' to do CPU-bound work on a background thread.

编译器实际上是在建议这个解决方案:

public class SimplyAwesome : IAmAwesome {
    public async Task MakeAwesomeAsync() {
        await Task.Run(() => {
            // Some really awesome stuff here, but on a BACKGROUND THREAD! WOW!
        });
    }
}

我的问题是 - 当我选择忽略此编译器警告时应该确定什么?在某些情况下,工作是如此简单,以至于为其生成一个线程无疑会适得其反。

如果您真的想同步完成工作,您知道您的 async 方法将始终 运行 同步,这在这种情况下是可取的,那么请务必忽略警告.如果您理解警告告诉您的内容并且认为它描述的操作是正确的,那么这不是问题。毕竟这是警告而不是错误是有原因的。

当然,另一种选择是不使用方法 async 而是简单地使用 Task.FromResult 到 return 一个已经完成的任务。它会改变错误处理语义(除非你也捕获所有异常并将它们包装到你 return 的任务中)所以至少要注意这一点。如果您确实希望通过结果 Task 传播异常,则可能值得保留方法 async 并只抑制警告。

What should determine when I choose to ignore this compiler warning? In some cases the work is so simple that spawning a thread for it is undeniably counter-productive.

编译器并没有说 "use Task.Run inside this method"。它只是告诉你你为他准备了一个 async 方法,在你的方法声明中添加了 async 修饰符,但你实际上并没有等待任何东西。

您可以采用三种方法:

一个。您可以忽略编译器警告,一切仍将执行。请注意,状态机生成会有轻微的开销,但您的方法调用将同步执行。如果该操作耗时并且可能导致方法执行进行阻塞调用,这可能会使使用此方法的用户感到困惑。

乙。将"Awesome"的生成分为同步接口和异步接口:

public interface MakeAwesomeAsync
{
    Task MakeAwesomeAsync();
}

public interface MakeAwesome
{
    void MakeAwesome();
}

C。如果操作不太耗时,您可以使用 Task.FromResult 将其简单地包装在 Task 中。在选择这个之前,我肯定会测量 运行 CPU 绑定操作需要多长时间。

正如其他人已经指出的那样,您有 3 种不同的选择,所以这是一个见仁见智的问题:

  1. 保留它 async 并忽略警告
  2. 有同步/async 重载
  3. 删除 async 和 return 一个已完成的任务。

我会推荐 returning already completed task:

public class SimplyAwesome : IAmAwesome 
{
    public Task MakeAwesomeAsync() 
    {
        // run synchronously
        return TaskExtensions.CompletedTask;
    }
}

有几个原因:

  • 创建方法 async 轻微 创建状态机和禁用某些优化(如内联)的开销,因为编译器的 try-catch 块添加。
  • 您需要处理警告,并与任何其他查看此代码的团队成员一起思考 await 在哪里。
  • 标记完全同步的方法有些不协调 async。就好像在你的代码中加入while(false){}一样,作用是一样的,只是没有传达出方法的意思。

Servy 指出 return执行任务会改变异常处理的语义。虽然这是真的,但我认为这不是问题。

首先,大多数 async 代码调用方法并在同一位置(即 await MakeAwesomeAsync())等待 returned 任务,这意味着异常将在无论方法是否 async 都在同一个地方。

其次,即使是 .Net 框架的 Task-returning 方法也会同步抛出异常。以 Task.Delay 为例,其中 throws an exception directly without storing it in the returned task,因此不需要 await 引发异常的任务:

try
{
    Task.Delay(-2);
}
catch (Exception e)
{
    Console.WriteLine(e);
}

由于 .Net 开发人员需要 except 在 .Net 中同步遇到异常,因此他们也应该从您的代码中排除异常。