使用async / await进行设计 - 一切都应该是异步的吗?

时间:2016-09-04 20:40:16

标签: c# multithreading asynchronous async-await

假设我有一个实现为

的接口方法
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,因为:

  1. 它在服务器应用程序中消耗ThreadPool个帖子
  2. 导致死锁:如果所有ThreadPool个线程都是Wait,则没有线程可以完成任何任务。
  3. 所以问题是:即使我不打算在内部调用任何异步,我是否应该最初制作我的所有方法async?不会损害性能吗?或者如何设计东西以避免这种难以重构?

2 个答案:

答案 0 :(得分:17)

  

即使我不打算在内部调用任何异步内容,我是否应该首先使我的所有方法始终异步同步?

async的此设计问题与IDisposable的问题基本相同。也就是说,接口来预测它们的实现。无论你做什么,这都会很混乱。

根据我的经验,考虑一个方法/接口/类并预测它是否会使用I / O通常是相当简单的。如果它需要I / O,那么它可能应该返回任务。有时(但不总是),可以构建代码,以便I / O在代码的自己的部分完成,使业务对象和逻辑严格同步。 JavaScript世界中的Redux模式就是一个很好的例子。

但是底线,有时你做错了电话并且必须重构。我认为这是一个更好的方法,而不仅仅是让每个方法异步。您是否使每个接口继承自IDisposable并在任何地方使用using?不,你只在必要时添加它;你应该采用与async相同的方法。

答案 1 :(得分:3)

  

即使我不打算在内部调用任何异步内容,我是否应该首先使我的所有方法完全异步?

不,你不应该。让一切async伤害阅读,写作和理解你的代码,即使只是一点点。它也可能会损害代码的性能,特别是如果你真的做了每一个方法(包括属性吗?)async

  

如何设计东西以避免这种难以重构?

在某种程度上,这种重构可能是必要的。

但是,您可以使用单一责任原则正确构建代码,从而避免许多痛苦。检查状态是否正确的方法绝对不应该发送聊天消息。这样,将一种方法更改为async不会影响太多代码。