返回任务的接口的同步实现



类似于实现一个需要在同步代码中返回类型的接口,尽管我很好奇我是否应该忽略我的情况生成的编译器错误。

假设我有一个这样的界面:

public interface IAmAwesome {
    Task MakeAwesomeAsync();
}

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

在其他情况下,也许很少见,只需要一个简单的同步方法就可以变得很棒。因此,让我们假设实现如下所示:

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

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

此方法缺少"等待"运算符,将同步运行。 请考虑使用 await 运算符来等待非阻塞 API 调用, 或"等待 TaskEx.Run(...)"在后台执行 CPU 密集型工作 线。

编译器实际上建议此解决方案:

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 来返回已完成的任务。 它会改变错误处理语义(除非你还捕获所有异常并将它们包装到你返回的任务中),所以至少要注意这一点。 如果您确实希望异常通过生成的Task传播,则可能值得将该方法保留async并仅禁止显示警告。

当我选择忽略此编译器警告时,应该确定什么? 在某些情况下,工作非常简单,以至于为其生成线程是 不可否认适得其反。

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

您可以采取三种方法:

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

B.将"Awesome"的生成分离为同步接口和异步接口:

public interface MakeAwesomeAsync
{
    Task MakeAwesomeAsync();
}
public interface MakeAwesome
{
    void MakeAwesome();
}

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

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

  1. 保持async并忽略警告
  2. 具有同步/async重载
  3. 删除async并返回已完成的任务。

我建议返回一个已经完成的任务:

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

原因如下:

  • 由于编译器添加了 try-catch 块,将方法设为async在创建状态机和禁用某些优化(如内联)方面会产生轻微的开销。
  • 您需要处理警告以及任何其他查看此代码的团队成员想知道await在哪里。
  • 将完全同步的方法标记为async有些不和谐。这就像在代码中添加while(false){}一样,它的作用相同,但它不会传达方法的含义。

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

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

其次,即使是 .Net 框架的 Task-return 方法也会同步抛出异常。以 Task.Delay 为例,它直接抛出异常而不将其存储在返回的任务中,因此无需await任务来引发异常:

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

由于 .Net 开发人员需要在 .Net 中同步遇到异常,因此他们应该从代码中遇到异常是合理的。

最新更新