类似于实现一个需要在同步代码中返回类型的接口,尽管我很好奇我是否应该忽略我的情况生成的编译器错误。
假设我有一个这样的界面:
public interface IAmAwesome {
Task MakeAwesomeAsync();
}
在某些实现中,使用 async
和 await
异步完成带来了巨大的好处。这确实是接口试图允许的。
在其他情况下,也许很少见,只需要一个简单的同步方法就可以变得很棒。因此,让我们假设实现如下所示:
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 种不同的选择,因此这是一个意见问题:
- 保持
async
并忽略警告 - 具有同步/
async
重载 - 删除
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 中同步遇到异常,因此他们应该从代码中遇到异常是合理的。