返回Task的接口的同步实现
类似于在同步代码中实现需要Task返回类型的接口,虽然我很好奇我是否应该忽略编译器错误,而是我的情况生成。
假设我有这样的界面:
public interface IAmAwesome { Task MakeAwesomeAsync(); }
在某些实现中,使用async
和await
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种不同的选择,所以这是一个观点问题:
- 保持
async
并忽略警告 - 有同步/
async
重载 - 删除
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,因此除了代码之外,它们也应该是合理的。