#c_sharp #async_await #асинхронность
Есть консольное приложение, которое принимает входящие подключения, считывает команду, выполняет ее, и возвращает ответ (или выполняет без ответа). Область деятельности у приложения такая: 1) Http запросы. 2) Запросы к базе данных. 3) Отправка команд по сокетам в другое консольное приложение. Перерыл много статей в интернете, где написано, что использование ConfigureAwait(false) недопустимо в GUI приложении, т.к. там требуется исходный контекст после выполнения асинхронной функции. У меня консольное приложение, в котором, как я понял, не важно в каком контексте продолжит выполняться функция. Правильно ли я думаю? Может ли повсеместное использование ConfigureAwait(false) в моем приложении обернуться боком? А конкретно: 1) Возможна ли взаимоблокировка (Deadlock). 2) Увеличение нагрузки на систему / уменьшение производительности. 3) Выполнение асинхронных функций не по заданному порядку (вычитал и такое в одной статье). 4) Или какая-нибудь другая проблема, про которую я не знаю. Пример кода из программы: private async Task UpdateUserBalance(int id, decimal amount) { Transaction transaction = await DataBase.StartTransactionAsync().ConfigureAwait(false); try { UserModel user = await UserModel.FindAsync(id, transaction).ConfigureAwait(false); user.balance += amount; await user.Update(transaction).ConfigureAwait(false); await transaction.CommitAsync().ConfigureAwait(false); } catch (Exception e) { await transaction.RollbackAsync().ConfigureAwait(false); } finally { transaction.Dispose(); } } Есть так же функции async void, которые вызывают недоверие по отношению к deadlock-ам. Пример: class TimerClass { private Timer _timer; // using System.Timers; public TimerClass() { _timer = new Timer(5 * 1000); _timer.Elapsed += Callback; _timer.AutoReset = true; _timer.Enabled = true; _timer.Start(); } private async void Callback(object state, ElapsedEventArgs e) { // code await SomeAsyncFunc().ConfigureAwait(false); // code } } И еще вопрос. Возможен ли deadlock в этом коде? Т.к используется task.Result. Listtasks = new List (); for(int i = 0; i < someLength; i++) { tasks.Add(SomeTaskFunc()); } await Task.WhenAll(tasks); for(int i = 0; i < tasks.Count; i++) { Task task = tasks[i]; // Дальше идут операции с task.Result }
Ответы
Ответ 1
Формально говоря, для консольного приложения или сервиса повсеместное использование ConfigureAwait(false) действительно не нужно. Поскольку контекст синхронизации все равно отсутствует, то нет нужны в привязке конкретного кода к конкретному контексту (как в случае с UI приложением) -- весь код будет работать в пуле потоков. Однако я бы все равно рекомендовал использовать ConfigureAwait(false) везде, где возможно, по двум причинам: Вы повысите переиспользуемость кода. Если код будет затем перенесен в GUI приложение, он сразу будет работать правильно. Если вам или используемой вами библиотеке понадобится установить свой контекст, ваш код продолжит работать корректно (по крайней мере, не изменит своего поведения). Отвечая на конкретные вопросы: Нет. До тех пор, пока у вас отсутствует контекст синхронизации (либо он есть, но без message loop) и вы не используете блокирующие вызовы типа Result и Wait(), можете забыть о дедлоках. Не будет. Не вижу связи (ссылка на статью помогла бы прояснить вопрос). Что касается async void методов, то главная опасность их заключается в двух вещах, которые с ConfigureAwait(false) никак не связаны: Исключения, возникшие в таких методах, не обрабатываются. Для таймера и консольного приложения это будет означать завершение процесса. Поэтому нужно либо искать полностью асинхронные альтернативы, либо использовать в колбэке глобальный try/catch. Такой метод завершает свое выполнение, грубо говоря, сразу после первого await. Т.о., если сам асинхронный вызов или код, находящийся после асинхронного вызова, занимает значительно время, то вызовы колбэка по таймеру могут накладываться друг на друга. Обойти это можно, если каждый раз выключать таймер в начале метода, и снова включать в конце метода. UPD Возможен ли deadlock в этом коде? Т.к используется task.Result. Блокирующее ожидание таска (через Result или Wait()) само по себе не гарантирует вам дедлок! Все зависит от контекста и от того, что происходит внутри вызываемого метода. Если внутри SomeTaskFunc() нет ничего криминального, то дедлок невозможен хотя бы потому, что к моменту обращения к Result все таски уже завершили свою работу (await Task.WhenAll(tasks)). А еще потому, что у вас консольное приложение и вы пользуетесь одним контекстом. Про блокирующее ожидание и дедлоки почитайте в этом вопросе. P.S. написано, что использование ConfigureAwait(false) недопустимо в GUI приложении, т.к. там требуется исходный контекст после выполнения асинхронной функции Не совсем корректное утверждение. Использовать ConfigureAwait(false) в GUI приложениях нужно везде, где нет необходимости выполнять продолжение в UI потоке. Т.е. опускать ConfigureAwait(false) нужно только в UI коде. Везде дальше по дереву вызовов присутствие ConfigureAwait(false) крайне желательно (читай, обязательно).
Комментариев нет:
Отправить комментарий