Недавно я знатно объебался при написании кода. Прод не пострадал только благодаря тому, что нагрузка выявила рост потребления памяти и времени ответа. Время ответа выросло в два раза, а память на 20% подросла.
Оказалось все дело было в том, что я, уважаемые граждане, тупой и не знаю. Но теперь я умный и знаю и сейчас вам расскажу.
Предположим у вас есть такой метод:
public async Task ExecuteAsync(Func<Task> action)
{
await action();
}
✅Нормальный вариант вызова этого метода выглядит так:
public async Task Bar()
{
await ExecuteAsync(() => Task.Delay(1000));
}
❌Но вы можете вызвать его вот так:
public async Task Foo()
{
await ExecuteAsync(async () =>
{
await Task.Delay(1000);
});
}
😬И это будет ошибкой, но компилятор об этом никому не скажет и спокойно все прожует. Только вот вопрос, а что же тогда будет?
ℹ️ А будет вот что. Как вы (может быть) знаете async/await при использовании генерирует класс IAsyncStateMachine, в котором менеджерится продолжение работы вашего кода после оператора await. Так вот во втором случае таких классов сгенерируется не один, как в первом случае, а два! Отсюда и рост потребления памяти! Соответсвтенно увеличится и количество исполняемых инструкций после оператора await, что приводит и к замедлению работы метода. Такие дела!
🅰️Итого: не будьте тупыми, как я, и не передавайте асинхронные лямбды в методы с названием Async.
P.S.: А вот и ссылка на диффы между разными вариантами вызовов https://quickdiff.net/?unique_id=84CB0FCA-A15D-7D87-5747-80E13C087198