我有一个简单的界面
public interface SomethingProvider
{
public Something GetSomething();
}
要“异步”,我会这样做
public interface SomethingProvider
{
public Task<Something> GetSomethingAsync();
}
虽然接口现在提示GetSomething
是异步的,但它允许同步执行,如果同步结果足够快,这很好。如果它阻塞,那么我可以将责任归咎于执行程序员对接口的不良实现。
因此,如果后一个接口是通过足够快的阻塞实现来实现的,那么后一个接口比前者更灵活,因此更受欢迎。
在这种情况下,如果调用方法的时间稍微超过一段时间,我是否应该重写所有界面以返回任务?
修改
我想强调的是,这不是关于任务是什么或它们如何工作的问题,而是与定义它们时接口的固有未知实现相关的设计问题。在编写接口时,同样允许同步和异步实现似乎是有益的。
第三个“解决方案”可能是前两个的一些合并:
public interface SomethingProvider
{
public Something GetSomething();
public Task<Something> GetSomethingAsync();
}
但这打破了利斯科夫的替代原则,只给实施者带来了混乱。
答案 0 :(得分:9)
此设计问题与IDisposable
并行。在编写界面时,您必须知道它是否可能&#34;派生类型需要IDisposable
,并且如果它们将从中派生出来。像测试存根这样的非一次性类型只实现noop Dispose
。
同样,当您编写界面时,您必须知道它是否可能&#34;派生类型将使用异步实现。
请注意,派生类型的问题是:&#34;实现是否会自然异步?&#34;, 不 &#34;是否会实施快点?&#34;。速度与它无关。您应该考虑的唯一事情是实现是否可以是自然异步的(即,使用I / O或其他异步方法)。
当您编写界面时,通常会考虑一个(或少数)实现;我建议你在决定使接口方法异步时只考虑那些。你可能变得极端,只是让每个接口方法都异步,但这就像在任何地方使用IDisposable
- 而不是我推荐的模式。
所以,我想说如果你的(当前)实现对于特定方法都是同步的,那么使该方法同步;否则,让它异步。
如您所述,异步方法签名在技术上意味着实现可能是异步的。测试存根等可以使用Task.FromResult
同步实现异步签名。就个人而言,我认为这对于存根实现是完全可以接受的;但是我不建议将方法设为异步&#34;以防万一&#34;,当所有当前实现都是同步时。
另外,我强烈反对同时签名。这将要求实现者包装sync-over-async或async-over-sync,这两者都不是理想的。
答案 1 :(得分:4)
这取决于您对接口方法的“快速”程度。例如,如果实现很简单,例如:
public Something GetSomething(){
var s = new Something();
if(...)s.SomeField = 1;
return s;
}
然后返回Task<>
方法会产生比性能提升更多的开销。方法很可能根本没有相当大的性能影响。
但是,如果它像查询DB,url或非常复杂的计算那样,例如:
public Something GetSomething(){
Something s = Cache.Get("some key");
if(s!=null)return s;
var con = new SqlConnection(...);
...;
return s;
}
然后你想要标记Task<>
。
简而言之,这是一个设计问题,在个案基础上应用,没有绝对的规则可循。
答案 2 :(得分:4)
使用Task<>
创建界面时,您要告诉接口的实施者:&#34;此方法将异步运行。它将触发将在未来的某个时间完成的工作&#34;。它不是关于如何&#34;快速&#34;该方法将运行,异步方法可以从Web请求快速返回。确实,异步操作中的await
将触发状态机的开销,但它确实无法保证&#34;速度&#34;执行。
根据执行的速度,你不应该用Task
包装你的所有方法。如果它们代表真正纯异步的代码,你应该标记它们。 < / p>
我喜欢来自What is the difference between task and thread?的Task
的定义(由于@ akshay2000略有修改)
在计算机科学术语中,任务是 future 或 promise 。 (有些人同义地使用这两个术语,有些人使用不同的术语,没有人能够就精确的定义达成一致。)基本上,任务&#34;承诺&#34;给你一个T,但现在不是蜂蜜,当我准备好的时候生病了回到你身边