假设我有一个实现为
的接口方法public void DoSomething(User user)
{
if (user.Gold > 1000) ChatManager.Send(user, "You are rich: " + user.Gold);
}
一段时间后,我意识到我想要改变它:
public async Task DoSomething(User user)
{
if (user.Gold > 1000) ChatManager.Send(user, "You are rich: " + user.Gold);
if (!user.HasReward)
{
using(var dbConnection = await DbPool.OpenConnectionAsync())
{
await dbConnection.Update(user, u =>
{
u.HasReward = true;
u.Gold += 1000;
});
}
}
}
我正在更改界面中的方法签名。但调用方法是同步的,我不仅要使它们异步,还要使整个调用树异步。
示例:
void A()
{
_peer.SendResponse("Ping: " + _x.B());
}
double X.B()
{
return _someCollection.Where(item => y.C(item)).Average();
}
bool Y.C(int item)
{
// ...
_z.DoSomething();
return _state.IsCorrect;
}
应改为
async void AAsync()
{
_peer.SendResponse("Ping: " + await _x.BAsync());
}
async Task<double> X.BAsync()
{
// await thing breaks LINQ!
var els = new List<int>();
foreach (var el in _someCollection)
{
if (await y.CAsync(item)) els.Add(el);
}
return _els.Average();
}
async Task<bool> Y.CAsync(int item)
{
// ...
await _z.DoSomething();
return _state.IsCorrect;
}
受影响的调用树可能非常大(许多系统和接口),因此很难做到这一点。
同样,当从A
等接口方法调用第一个IDisposable.Dispose
方法时 - 我无法使其异步。
另一个例子:假设对A
的多次调用被存储为委托。之前他们刚刚使用_combinedDelegate.Invoke()
进行了调用,但现在我应该对每个项目进行GetInvocationList()
和await
。
哦,并考虑用异步方法替换属性getter。
我无法使用Task.Wait()
或.Result
,因为:
ThreadPool
个帖子ThreadPool
个线程都是Wait
,则没有线程可以完成任何任务。所以问题是:即使我不打算在内部调用任何异步,我是否应该最初制作我的所有方法async
?不会损害性能吗?或者如何设计东西以避免这种难以重构?
答案 0 :(得分:17)
即使我不打算在内部调用任何异步内容,我是否应该首先使我的所有方法始终异步同步?
async
的此设计问题与IDisposable
的问题基本相同。也就是说,接口有来预测它们的实现。无论你做什么,这都会很混乱。
根据我的经验,考虑一个方法/接口/类并预测它是否会使用I / O通常是相当简单的。如果它需要I / O,那么它可能应该返回任务。有时(但不总是),可以构建代码,以便I / O在代码的自己的部分完成,使业务对象和逻辑严格同步。 JavaScript世界中的Redux模式就是一个很好的例子。
但是底线,有时你做错了电话并且必须重构。我认为这是一个更好的方法,而不仅仅是让每个方法异步。您是否使每个接口继承自IDisposable
并在任何地方使用using
?不,你只在必要时添加它;你应该采用与async
相同的方法。
答案 1 :(得分:3)
不,你不应该。让一切即使我不打算在内部调用任何异步内容,我是否应该首先使我的所有方法完全异步?
async
伤害阅读,写作和理解你的代码,即使只是一点点。它也可能会损害代码的性能,特别是如果你真的做了每一个方法(包括属性吗?)async
。
如何设计东西以避免这种难以重构?
在某种程度上,这种重构可能是必要的。
但是,您可以使用单一责任原则正确构建代码,从而避免许多痛苦。检查状态是否正确的方法绝对不应该发送聊天消息。这样,将一种方法更改为async
不会影响太多代码。