Async/Await в многослойных приложениях С#

У меня есть многоуровневое веб-приложение С# MVC4 в сценарии с высоким трафиком, в котором используется инъекция зависимостей для разных репозиториев. Это очень полезно, потому что оно легко проверяемо, и в процессе производства мы можем легко настроить хранилища для конкретных контроллеров. Все контроллеры наследуются от AsyncController, поэтому методы действий, возвращающие Task<JsonResult> или Task<ActionResult>, действительно помогают нашему серверу масштабироваться с большим количеством пользователей и решить проблему голодного нити. В некоторых хранилищах используются веб-службы, которые мы можем определенно использовать Async/Await для обеспечения преимуществ масштабируемости. В других случаях некоторые могут использовать неуправляемые службы, которые не могут быть надежно загружены, в которых async/await не будет предоставлять никаких преимуществ. Каждый репозиторий представляет собой реализацию интерфейса, IRepository. Короче говоря, каждая реализация сильно различается в способе получения данных. Эта конфигурация выбирается во время развертывания с изменениями web.config и модулями Autofac.

Каковы некоторые из рекомендуемых способов реализации async/wait в приложении, таком как это? Чтобы сделать шаблон подходящим в моем существующем приложении, я должен изменить интерфейс, чтобы вернуть Task<MyBusinessObject>. Но не больше ли это детали реализации? Я мог бы предоставить два окурка метода GetData и GetDataAsync, но для меня это не позволило бы гибкости, которая у меня есть теперь с каркасами IoC, такими как Autofac, где я могу легко заменить все, не изменяя код.

Один из разработчиков Microsoft для Async/Await опубликовал сообщение в блоге: " Должен ли я подвергать асинхронную оболочку для моих синхронных методов?" этот вопрос входит в проблему. Если ваш метод асинхронизации не является по-настоящему асинхронным (обеспечивающим собственные преимущества ввода-вывода), тогда он действительно только увеличивает накладные расходы. Другими словами, он не будет предоставлять никаких преимуществ масштабируемости.

Мне просто хотелось бы, чтобы остальная часть Сообщества занялась этой проблемой.

Ответ 1

В вашей ситуации я рекомендую сделать ваши интерфейсы асинхронными:

public interface IMyInterface
{
  Task<TMyResult> GetDataAsync();
}

Для синхронных методов вы можете вернуть Task.FromResult:

public class MyImplementation : IMyInterface
{
  public Task<TMyResult> GetDataAsync()
  {
    TMyResult ret = ...;
    return Task.FromResult(ret);
  }
}

Асинхронные методы могут быть, конечно, async и использовать await.

Что касается деталей реализации, я бы сказал "да и нет".;) Это очень похоже на IDisposable в этом отношении. Логически, это деталь реализации в том смысле, что не имеет значения, каким образом она реализована. Это не деталь реализации в том смысле, что вызывающий код должен обрабатывать его по-разному. Поэтому я согласился бы - логически - это деталь реализации, но платформа .NET недостаточно развита, чтобы рассматривать ее как таковую.

В качестве альтернативы вы можете думать об этом так (это объяснение, которое я обычно даю): async - это фактическая деталь реализации, а когда вы возвращаете Task<T> вместо T, вы определяете интерфейс где реализация может быть асинхронной.

У меня есть серия сообщений в блоге, которые адресуют проектирование с помощью async "в большом" (т.е. подгонка async in with ООП, а не позволять ему функционировать). Я рассматриваю некоторые общие проблемы, такие как асинхронная инициализация и IoC. Это всего лишь одно мнение человека, но я не знаю никаких других ресурсов для этих проблем.

P.S. Вам больше не нужно наследовать от AsyncController в MVC4. Обработчик контроллера по умолчанию автоматически обнаруживает методы async по их возвращаемому типу (Task или Task<T>).

Ответ 2

Задача - это нечеткая абстракция. Если вы хотите использовать потоки портов ввода-вывода ввода-вывода, вам придется возвращать задачи целиком из ваших репозиториев на ваши контроллеры.

Несмотря на то, что возврат всего столбца вызова Task<T> может улучшить масштабируемость, он добавляет дополнительный уровень сложности вашему приложению. Это будет препятствовать ремонтопригодности, тестируемости и затрудняет рассуждение о коде. Асинхронный код всегда будет сложнее, чем синхронный код.

Лично я скорее увеличиваю объем памяти сервера и настраиваю приложение на наличие большего пула потоков, чтобы компенсировать потерю масштабируемости или масштабировать до нескольких дополнительных (виртуальных) серверов вместо того, чтобы добавлять эту дополнительную сложность.