2016-09-04 2 views
10

Предположим, у меня есть метод интерфейса реализован какДизайн с асинхронным/ожиданием - должно ли быть все асинхронное?

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; 
         }); 
     } 
    } 
} 

Я меняю подпись метода в интерфейсе. Но вызывающие методы были синхронными, и я должен сделать не только их async, но и все дерево вызовов async.

Пример:

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 методом async.

я не могу использовать Task.Wait() или .Result, потому что:

  1. Это тратить ThreadPool потоков в приложении сервера
  2. Это приводит к тупиков: если все ThreadPool нити Wait ИНГ нет потоков для завершения любой задачи ,

Итак, вопрос в том, должен ли я сделать абсолютно все свои методы изначально async, даже если я не планирую вызывать что-либо асинхронное внутри? Разве это не повредит работе? Или как проектировать вещи, чтобы избежать таких сложных рефакторингов?

+0

У вас нет * have *, чтобы сделать методы вызова async - они могут просто выполнить вызов метода async как 'var result = SomeAsyncMethod(). Результат. Таким образом, вы можете разбить «цепочку асинхронизации», пока не будете готовы реорганизовать дальнейшее дерево вызовов. –

+0

* Разве это не повредит работе? * Вероятно, это не так. * Как разрабатывать вещи, чтобы избежать таких сложных рефакторингов? * Асинхронность несет цену - с одной стороны, вы можете лениво отложить все, но с другой стороны, для нетривиальных случаев для этого требуется код для восходящего потока (например, если ваш механизм проверки формы не поддерживает асинхронность, вы в основном SOL и должны блокировать его). –

+2

Заражение дерева вызовов - это вечная мука асинхронных кодовых баз. – usr

ответ

15

Должен ли я сделать абсолютно все мои методы изначально асинхронными, даже если я не планирую называть что-либо асинхронным внутри?

Эта проблема с async связана с проблемой IDisposable. То есть интерфейсы имеют, чтобы предсказать их реализацию. И это будет грязно, независимо от того, что вы делаете.

По моему опыту, довольно просто рассмотреть метод/интерфейс/класс и предсказать, будет ли он использовать ввод-вывод или нет. Если для этого требуется ввод-вывод, то, вероятно, это должно быть сделано с возвратом задачи. Иногда (но не всегда) можно структурировать свой код, чтобы операции ввода-вывода выполнялись в его собственном разделе кода, оставив бизнес-объекты и логику строго синхронными. Хорошим примером этого является шаблон Redux в мире JavaScript.

Но нижняя строка, иногда вы делаете неправильный вызов и имеете рефакторинг. Я думаю, что это гораздо лучший подход, чем просто делать каждый метод асинхронный. Вы делаете каждый интерфейс наследовать от IDisposable и использовать using всюду? Нет, ты добавляешь только когда это необходимо; и вы должны принять тот же подход с async.

+0

'IDisposable' намного проще: 1. часто объекты' IDisposable' используются только внутри методов (не хранятся как поле в любом месте), поэтому затрагиваются только прямые вызывающие. 2. Вызов 'Dispose' часто является необязательным, вы можете просто оставить вещи для финализатора; btw рассмотрите возможность добавления асинхронного вызова к существующему методу IDisposable.Dispose. – Vlad

+0

* Я имею в виду асинхронный вызов из 'Dispose' – Vlad

+0

Существуют ли какие-либо примеры C# для« структурирования вашего кода, чтобы операции ввода-вывода выполнялись в его собственном разделе кода, оставив бизнес-объекты и логику строго синхронными »? – Vlad

2

Должен ли я сделать абсолютно все мои методы изначально асинхронными, даже если я не планирую называть что-либо асинхронным внутри?

Нет, не следует. Делать все async болит читать, писать и понимать ваш код, пусть даже немного. Это может также повредить производительность вашего кода, особенно если вы действительно сделали каждый метод (включая свойства?) async.

Как проектировать вещи, чтобы избежать таких сложных рефакторингов?

В какой-то степени такие рефакторинги могут потребоваться.

Но вы можете избежать большой боли, правильно структурировав свой код, используя принцип единой ответственности.Метод, который проверяет правильность состояния, определенно не должен отправлять сообщения чата. Таким образом, изменение одного метода на async не должно влиять на слишком много кода.

Смежные вопросы