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

Vla*_*lad 17 c# multithreading asynchronous async-await

假设我有一个实现的接口方法

public void DoSomething(User user) 
{
    if (user.Gold > 1000) ChatManager.Send(user, "You are rich: " + user.Gold);
}
Run Code Online (Sandbox Code Playgroud)

过了一段时间,我意识到我想改变它:

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;
                        });
         }
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在更改界面中的方法签名.但调用方法是同步的,我不仅要使它们异步,还要使整个调用树异步.

例:

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;
}
Run Code Online (Sandbox Code Playgroud)

应改为

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;
}
Run Code Online (Sandbox Code Playgroud)

受影响的调用树可能非常大(许多系统和接口),因此很难做到这一点.

A从接口方法调用第一个方法时IDisposable.Dispose- 我无法使其异步.

另一个例子:假设多个调用A被存储为委托.以前他们只是叫用_combinedDelegate.Invoke(),但现在我应该去通过GetInvocationList(),并await在每个项目上.

哦,并考虑用异步方法替换属性getter.

我不能使用Task.Wait().Result因为:

  1. 这是ThreadPool在服务器应用程序中浪费线程
  2. 它导致死锁:如果所有ThreadPool线程Wait都没有线程来完成任何任务.

所以问题是:async即使我不打算在内部调用任何异步,我是否应该最初完全使用我的所有方法?它会不会影响性能?或者如何设计东西以避免这种难以重构?

Ste*_*ary 18

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

这个设计问题与问题async基本相同IDisposable.也就是说,接口必须预测它们的实现.不管你做什么,这都会很混乱.

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

但最重要的是,有时你会做出错误的调用并且必须重构.我认为这比使每个方法异步更好.你是否使每个接口继承IDisposableusing在任何地方使用?不,你只在必要时添加它; 你应该采取相同的方法async.

  • `IDisposable` 是一个更简单的情况: 1. 通常 `IDisposable` 对象仅在方法内部使用(不作为字段存储在任何地方),因此只有直接调用者受到影响。2. 调用“Dispose”通常是可选的,您可以将事情留给终结器。顺便说一句,请考虑向现有的“IDisposable.Dispose”方法添加异步调用。 (2认同)