Почему С# позволяет сделать переопределение async?
В С#, когда вы переопределяете метод, разрешается переопределять async, когда оригинальный метод не был. Это похоже на плохую форму.
Проблема, которая заставляет меня задуматься, заключается в следующем: я был вовлечен, чтобы помочь с проблемой тестирования нагрузки. Около 500 одновременных пользователей процесс входа в систему будет разбит на цикл перенаправления. IIS регистрировал исключения с сообщением "Асинхронный модуль или обработчик завершен, пока асинхронная операция все еще ожидает". Некоторые поиски заставили меня подумать, что кто-то злоупотреблял async void
, но мои быстрые поиски через источник ничего не нашли.
К сожалению, я искал async\s*void
(поиск в регулярном выражении), когда мне нужно было искать нечто вроде async\s*[^T]
(при условии, что Task не был полностью укомплектован.. вы поняли смысл).
То, что я позже обнаружил, было async override void onActionExecuting
в базовом контроллере. Понятно, что это была проблема, и это было так. Фиксация этого (что делает его синхронным на данный момент) разрешило проблему.
Но это оставило меня с вопросом: почему вы можете пометить переопределение как async, когда вызывающий код никогда не мог его ждать?
Ответы
Ответ 1
Когда базовый класс (или интерфейс) объявляет виртуальный метод, который возвращает задачу, вы можете переопределить его, пока вы возвращаете задачу. Ключевое слово async
- это всего лишь подсказка для компилятора, чтобы преобразовать ваш метод в конечный автомат. Хотя компилятор делает черную магию для вашего метода, скомпилированный метод все еще возвращает задачу.
Что касается виртуальных методов void
, вы можете переопределить одно без ключевое слово async
(очевидно) и запустить в нем не ожидаемую задачу. То, что происходит, когда вы переопределяете его с ключевым словом async
и используете await
в теле. Вызывающий не дождался созданной задачи (так как "оригинальная" подпись void
). Оба случая аналогичны *:
public override void MyVirtualMethod()
{
// Will create a non awaited Task (explicitly)
Task.Factory.StartNew(()=> SomeTaskMethod());
}
public override async void MyVirtualMethod()
{
// Will create a non awaited Task (the caller cannot await void)
await SomeTaskMethod();
}
Статья Стивена Клири содержит некоторые примечания относительно этого:
- Авансовые методы, возвращающие Void, имеют определенную цель: сделать возможным асинхронные обработчики событий.
- Вы должны предпочесть async Task для async void.
* Реализация SomeTaskMethod
, базовая структура, SynchronizationContext и другие факторы могут и будут приводить к различным результатам для каждого из вышеуказанных методов.
Ответ 2
Вы можете переопределить метод async
, потому что async не является частью сигнатуры метода. На самом деле async позволяет использовать ключевое слово await
в вашем методе, создав в нем конечный автомат.
Вы можете найти дополнительную информацию об асинхронном использовании здесь: http://blog.sublogic.com/2012/05/14/async-isnt-really-part-of-your-method-signature/
Ответ 3
async
не входит в "контракт". Это деталь реализации, которая, на мой взгляд, к сожалению, появляется не в том месте.
Совершенно правомерно изменить метод (не async
), возвращая Task
в async
один (или наоборот) без изменения прерывания и не требуя повторной компиляции вызывающих абонентов.
Дальнейшее указание на то, что он не является частью контракта, заключается в том, что вам не разрешено отмечать функции как async
внутри интерфейсов и, как здесь, вполне возможно переопределить async
с помощью async
или наоборот.