Ответ 1
Я что-то пропустил?
Да. Асинхронный код - по своей природе - подразумевает, что текущий поток не используется во время выполнения операции. Синхронный код - по своей природе - подразумевает, что текущий поток блокируется во время выполнения операции. Вот почему вызов асинхронного кода из синхронного кода буквально даже не имеет смысла. Фактически, как я описываю в своем блоге, наивный подход (с использованием Result
/Wait
) может легко привести к взаимоблокировкам.
Первое, что нужно учитывать: должен ли мой API быть синхронным или асинхронным? Если он касается ввода-вывода (как в этом примере), он должен быть асинхронным. Таким образом, это будет более подходящий дизайн:
public async Task<string> RetrieveHolidayDatesFromSourceAsync() {
var result = await this.DoRetrieveHolidayDatesFromSourceAsync();
/** Do stuff **/
var returnedResult = this.TransformResults(result); /** Where result gets used **/
return returnedResult;
}
Как я описал в своей статье статью о лучшей асинхронной практике, вы должны пойти "асинхронно". Если вы этого не сделаете, вы все равно не получите какую-либо выгоду от асинхронизации, так зачем беспокоиться?
Но позвольте сказать, что вы заинтересованы в том, чтобы в конечном итоге перейти к async, но сейчас вы не можете все изменить, просто хотите изменить часть своего приложения. Это довольно распространенная ситуация.
В этом случае правильный подход заключается в том, чтобы разоблачать как синхронные, так и асинхронные API. В конце концов, после обновления всего кода, синхронные API-интерфейсы могут быть удалены. В статье о разработке асинхронных проектов на брандмауэре я исследую множество вариантов такого сценария; моим личным фаворитом является "bool parameter hack", который будет выглядеть следующим образом:
public string RetrieveHolidayDatesFromSource() {
return this.DoRetrieveHolidayDatesFromSourceAsync(sync: true).GetAwaiter().GetResult();
}
public Task<string> RetrieveHolidayDatesFromSourceAsync() {
return this.DoRetrieveHolidayDatesFromSourceAsync(sync: false);
}
private async Task<string> DoRetrieveHolidayDatesFromSourceAsync(bool sync) {
var result = await this.GetHolidayDatesAsync(sync);
/** Do stuff **/
var returnedResult = this.TransformResults(result);
return returnedResult;
}
private async Task<string> GetHolidayDatesAsync(bool sync) {
using (var client = new WebClient()) {
return sync
? client.DownloadString(SourceURI)
: await client.DownloadStringTaskAsync(SourceURI);
}
}
Этот подход позволяет избежать дублирования кода, а также позволяет избежать любых проблем взаимоблокировки или повторного размещения, общих с другими решениями для устранения неполадок "sync-over-async".
Обратите внимание, что я все равно обработаю полученный код как "промежуточный шаг" на пути к правильно-асинхронному API. В частности, внутренний код должен был вернуться на WebClient
(который поддерживает как синхронизацию, так и асинхронный) вместо предпочтительного HttpClient
(который поддерживает только async). После того, как весь код вызова будет изменен для использования RetrieveHolidayDatesFromSourceAsync
, а не RetrieveHolidayDatesFromSource
, я бы пересмотрел его и удалил весь технический долг, изменив его на использование HttpClient
и получив асинхронный доступ.