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>
может улучшить масштабируемость, он добавляет дополнительный уровень сложности вашему приложению. Это будет препятствовать ремонтопригодности, тестируемости и затрудняет рассуждение о коде. Асинхронный код всегда будет сложнее, чем синхронный код.
Лично я скорее увеличиваю объем памяти сервера и настраиваю приложение на наличие большего пула потоков, чтобы компенсировать потерю масштабируемости или масштабировать до нескольких дополнительных (виртуальных) серверов вместо того, чтобы добавлять эту дополнительную сложность.