Эффективное смешивание методов синхронизации и асинхронности в одном методе?

11

Хорошо, это звучит странно, но код очень прост и хорошо объясняет ситуацию.

public virtual async Task RemoveFromRoleAsync(AzureTableUser user, string role)
{
    AssertNotDisposed();
    var roles = await GetRolesForUser(user);
    roles.Roles = RemoveRoles(roles.Roles, role);
    await Run(TableOperation.Replace(roles));
}

(Я знаю, что говорю вроде в абстрактном ниже, но выше это фактический метод из того, что будет на самом деле производственный код, который на самом деле делает то, о чем я здесь спрашиваю, и я на самом деле заинтересован в том, чтобы вы на самом деле рецензирование это для корректности по отношению к шаблону асинхронности / ожидания.)

Я сталкиваюсь с этим паттерном все чаще и чаще сейчас, когда использую async/ awaitбольше. Шаблон состоит из следующей цепочки событий:

  1. Жду первоначального звонка, который дает мне некоторую информацию, с которой мне нужно поработать
  2. Работать с этой информацией синхронно
  3. Жду окончательного звонка, который сохраняет обновленную работу

Приведенный выше блок кода, как правило, относится к обработке этих методов. Я awaitпервый звонок, который я должен, потому что это асинхронный. Затем я выполняю работу, которая мне необходима, которая не связана с вводом-выводом или ресурсами, и поэтому не асинхронна. Наконец, я сохраняю свою работу, которая также является asyncвызовом, и из-за груза культа я awaitего.

Но является ли это наиболее эффективным / правильным способом обработки этого паттерна? Мне кажется, я мог бы пропустить awaitпоследний звонок, но что, если он потерпит неудачу? И должен ли я использовать такой Taskметод, ContinueWithчтобы связать мою синхронную работу с исходным вызовом? Я сейчас нахожусь в точке, где я не уверен, правильно ли я это делаю.

Учитывая код в примере , есть ли лучший способ обработать эту цепочку вызовов метода async / sync / async?

Сорвал
источник
Код кажется мне максимально коротким и понятным. Все, что я могу придумать, вносит ненужную сложность.
Эйфорический
@Euphoric: Должен ли я ждать моего последнего звонка? Что бы случилось, если бы я этого не сделал и его бросили? Будет ли по-другому, как сейчас?
Сорвано

Ответы:

3

Да, я думаю, что это правильный способ сделать это.

Вы не можете пропустить второй await. Если бы вы это сделали, метод, казалось бы, завершился слишком рано (до того, как на самом деле было выполнено удаление), и вы никогда бы не узнали, не удалось ли удалить.

Я не вижу, как ContinueWith()или что-то подобное поможет здесь. Вы можете использовать его, чтобы избежать использования await, но это сделает ваш код более сложным и менее читабельным. И в этом весь смысл await: упростить написание асинхронного кода по сравнению с использованием продолжений.

svick
источник
0

Способ обработки этого шаблона состоит в том, чтобы гарантировать, что весь ввод / вывод является асинхронным. Методы синхронного ввода / вывода заставляют текущий поток блокироваться, пока он ожидает ответа от пункта назначения ввода / вывода (сеть, файловая система и т. Д.).

Еще одна вещь, которую следует учитывать, это то, что awaitследует использовать, когда вам нужно возвращаемое значение или когда вам нужно завершить ожидаемый код, прежде чем делать что-то еще. Если вам не нужна ни одна из этих вещей, вы можете «запустить и забыть» свой асинхронный метод с помощью Task.Run.

Таким образом, для наиболее эффективного использования вычислительных ресурсов, если RemoveRolesвыполняется какой-либо ввод-вывод, он должен стать, await RemoveRolesAsyncи методы ввода-вывода, вызываемые посредством, RemoveRolesAsyncтакже должны быть асинхронными (и, возможно, ожидаемыми).

Если производительность не является вашей главной заботой, тогда можно выполнить синхронный ввод-вывод в асинхронном потоке. Это технический долг, хотя. (В этом случае вам может потребоваться вызвать первый асинхронный метод в ConfigureAwaitзависимости от того, где выполняется код.)

Вот более детальный взгляд на лучшие практики - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Вот некоторые заметки о поведении ConfigureAwait в различных средах, таких как ASP.NET, WebAPI и т. Д. - /programming/13489065/best-practice-to-call-configureawait-for-all-server-side -код

Уэйн Блосс
источник
2
Вы никогда не должны запускать и забывать код с Task.Run. Все задачи должны быть ожидаемыми. Если вы не ожидаете Задачу, и Задача собирается сборщиком мусора в состоянии, когда исключение является «ненаблюдаемым», это приведет к возникновению исключения UnobservedTaskException во время выполнения и может привести к сбою приложения в зависимости от версии платформы.
Трийнко