返回Task的接口的同步实现

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

假设我有这样的界面:

public interface IAmAwesome { Task MakeAwesomeAsync(); } 

在某些实现中,使用asyncawait async完成可以带来很多好处。 这实际上是接口试图允许的内容。

在其他情况下,也许很少见,只需要一个简单的同步方法来制作真棒。 所以让我们假设实现如下:

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

这有效,但编译器警告:

这种方法缺少’await’运算符并将同步运行。 考虑使用await运算符等待非阻塞API调用,或者’await 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来返回已经完成的任务。 它会改变error handling语义(除非你还捕获所有exception并将它们包装到你返回的任务中),所以至少要注意这一点。 如果您确实希望通过生成的Task传播exception,则可能值得将方法保持为async并且仅抑制警告。

当我选择忽略此编译器警告时应该确定什么? 在某些情况下,工作非常简单,为它产生一个线程无疑会适得其反。

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

你可以采取三种方法:

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

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

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

C.如果操作不是太耗时,您可以使用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指出,返回任务会改变exception处理的语义。 虽然这是真的,但我认为这不是问题。

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

其次,甚至.Net框架的任务返回方法也会同步抛出exception。 以Task.Delay ,它直接抛出exception而不将其存储在返回的任务中 ,因此无需await任务引发exception:

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

由于.Net开发人员需要除了在.Net中同步遇到exception,因此除了代码之外,它们也应该是合理的。