Механизм async
/ await
позволяет компилятору преобразовать ваш код в конечный автомат. Ваш код будет работать синхронно до тех пор, пока первый await
не достигнет ожидающего, который еще не завершен, если таковой имеется.
В компиляторе Microsoft C # этот конечный автомат является типом значения, что означает, что он будет иметь оченьнебольшая стоимость, когда все await
s получат завершенные ожидаемые объекты, поскольку они не будут выделять объект и, следовательно, не будут создавать мусор. Если какое-либо ожидаемое не завершено, этот тип значения неизбежно помещается в квадрат.
Обратите внимание, что это не исключает выделения Task
с, если это тип ожидаемых, используемых в выражениях await
.
С ContinueWith
вы избегаете выделений (кроме Task
), только если у вашего продолжения нет замыкания и если вы либо не используете объект состояния, либо повторно используете состояниеОбъект как можно больше (например, из пула).
Кроме того, продолжение вызывается, когда задача завершается, создавая кадр стека, он не становится встроенным. Фреймворк пытается избежать переполнения стека, но может случиться так, что он не избежит его, например, когда большие массивы выделяются из стека.
Способ, которым он пытается избежать этого, заключается в проверке объема стека. слева и, если по какой-то внутренней мере стек считается заполненным, он планирует продолжение в планировщике задач. Он пытается избежать фатальных исключений переполнения стека за счет производительности.
Здесь есть небольшая разница между async
/ await
и ContinueWith
:
async
/ await
запланирует продолжения в SynchronizationContext.Current
, если таковые имеются, в противном случае в TaskScheduler.Current
1
ContinueWith
запланирует продолжения в предоставленномпланировщик задач или в TaskScheduler.Current
в перегрузках без параметра планировщика задач
Для имитации поведения async
/ await
по умолчанию:
.ContinueWith(continuationAction,
SynchronizationContext.Current != null ?
TaskScheduler.FromCurrentSynchronizationContext() :
TaskScheduler.Current)
Для имитации поведения async
/ await
с Task
* .ConfigureAwait(false)
:
.ContinueWith(continuationAction,
TaskScheduler.Default)
Вещи начинают усложняться циклами и обработкой исключений. Помимо обеспечения читабельности вашего кода, async
/ await
работает с любыми ожидаемыми .
Ваш случай лучше всего обрабатывать смешанным подходом: синхронный метод, который при необходимости вызывает асинхронный метод,Пример вашего кода с таким подходом:
public Task<SomeObject> GetSomeObjectByTokenAsync(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return Task.FromResult(new SomeObject()
{
IsAuthorized = false
});
}
else
{
return InternalGetSomeObjectByTokenAsync(repository, token);
}
}
internal async Task<SomeObject> InternalGetSomeObjectByToken(Repository repository, string token)
{
SomeObject result = await repository.GetSomeObjectByTokenAsync(token);
result.IsAuthorized = true;
return result;
}
По моему опыту, я нашел очень мало мест в приложении коде, где добавление такой сложности действительно окупаетсяразрабатывать, анализировать и тестировать такие подходы, в то время как в коде library любой метод может быть узким местом.
Единственный случай, когда я склонен выполнять задачи elide, - это когда Task
или Task<T>
Метод return просто возвращает результат другого асинхронного метода, не выполнив сам ввод-вывод или пост-обработку.
YMMV.
- Если вы не используете
ConfigureAwait(false)
или ждите какой-нибудь ожидающей, которая использует пользовательское планирование