Изменить: Этот вопрос выглядит, как будто это может быть та же проблема, но не имеет ответов ...
Изменить: в тестовом примере 5 задача застряла в WaitingForActivation
состоянии.
Я столкнулся с каким-то странным поведением при использовании System.Net.Http.HttpClient в .NET 4.5, где «ожидание» результата вызова (например) httpClient.GetAsync(...)
никогда не вернется.
Это происходит только при определенных обстоятельствах при использовании новых функций языка async / await и API задач - кажется, код всегда работает при использовании только продолжений.
Вот некоторый код, который воспроизводит проблему - поместите его в новый «проект MVC 4 WebApi» в Visual Studio 11, чтобы предоставить следующие конечные точки GET:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
Каждая из конечных точек здесь возвращает одни и те же данные (заголовки ответа от stackoverflow.com), за исключением тех, /api/test5
которые никогда не завершаются.
Сталкивался ли я с ошибкой в классе HttpClient, или я каким-то образом неправильно использую API?
Код для воспроизведения:
public class BaseApiController : ApiController
{
/// <summary>
/// Retrieves data using continuations
/// </summary>
protected Task<string> Continuations_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
}
/// <summary>
/// Retrieves data using async/await
/// </summary>
protected async Task<string> AsyncAwait_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return result.Content.Headers.ToString();
}
}
public class Test1Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await Continuations_GetSomeDataAsync();
return data;
}
}
public class Test2Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = Continuations_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test3Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return Continuations_GetSomeDataAsync();
}
}
public class Test4Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await AsyncAwait_GetSomeDataAsync();
return data;
}
}
public class Test5Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = AsyncAwait_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test6Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return AsyncAwait_GetSomeDataAsync();
}
}
источник
HttpClient.GetAsync(...)
?Ответы:
Вы неправильно используете API.
Вот ситуация: в ASP.NET только один поток может обрабатывать запрос одновременно. При необходимости можно выполнить некоторую параллельную обработку (заимствование дополнительных потоков из пула потоков), но только один поток будет иметь контекст запроса (дополнительные потоки не имеют контекста запроса).
Это управляется ASP.NET
SynchronizationContext
.По умолчанию, когда вы , метод возобновляется на захваченный (или не захвачен , если нет ). Обычно это как раз то, что вы хотите: асинхронное действие контроллера будет чем-то, и когда оно возобновится, оно возобновится с контекстом запроса.
await
Task
SynchronizationContext
TaskScheduler
SynchronizationContext
await
Итак, вот почему
test5
не получается:Test5Controller.Get
выполняетсяAsyncAwait_GetSomeDataAsync
(в контексте запроса ASP.NET).AsyncAwait_GetSomeDataAsync
выполняетсяHttpClient.GetAsync
(в контексте запроса ASP.NET).HttpClient.GetAsync
возвращает незавершенныйTask
.AsyncAwait_GetSomeDataAsync
ждетTask
; поскольку он не завершен,AsyncAwait_GetSomeDataAsync
возвращает незавершенныйTask
.Test5Controller.Get
блокирует текущий поток, пока он неTask
завершится.Task
возвращаемыйHttpClient.GetAsync
завершается.AsyncAwait_GetSomeDataAsync
пытается возобновить в контексте запроса ASP.NET. Однако в этом контексте уже существует поток: поток заблокирован вTest5Controller.Get
.Вот почему другие работают:
test1
,test2
иtest3
):Continuations_GetSomeDataAsync
планирует продолжение в пул потоков вне контекста запроса ASP.NET. Это позволяет завершитьTask
возвратContinuations_GetSomeDataAsync
без необходимости повторно вводить контекст запроса.test4
Аtest6
): Так какTask
это ожидали , запрос поток ASP.NET не заблокирован. Это позволяетAsyncAwait_GetSomeDataAsync
использовать контекст запроса ASP.NET, когда он готов продолжить.И вот лучшие практики:
async
методах используйте поConfigureAwait(false)
возможности. В вашем случае это изменитсяAsyncAwait_GetSomeDataAsync
наvar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Task
с; этоasync
все вниз. Другими словами, используйтеawait
вместоGetResult
(Task.Result
иTask.Wait
также следует заменить наawait
).Таким образом, вы получаете оба преимущества: продолжение (оставшаяся часть
AsyncAwait_GetSomeDataAsync
метода) запускается в базовом потоке пула потоков, который не должен входить в контекст запроса ASP.NET; и сам контроллерasync
(который не блокирует поток запроса).Больше информации:
async
/await
вступительный пост , который включает в себя краткое описание того, какTask
используют ожидающиеSynchronizationContext
.SynchronizationContext
ограничивает контекст запроса , чтобы только один поток одновременно.Обновление 2012-07-13: Включите этот ответ в сообщение в блоге .
источник
SynchroniztaionContext
, объясняющая, что для какого-то запроса в контексте может быть только один поток? Если нет, я думаю, что должно быть.SynchronizationContext
предоставляет некоторые важные функции: он передает контекст запроса. Это включает в себя все виды вещей от аутентификации до куки-файлов и культуры. Таким образом, в ASP.NET вместо синхронизации с пользовательским интерфейсом выполняется синхронизация с контекстом запроса. Это может измениться в ближайшее время: у новогоApiController
действительно естьHttpRequestMessage
контекст как свойство - таким образом, может не потребоваться проходить контекст черезSynchronizationContext
- но я еще не знаю.Изменить: как правило, старайтесь избегать выполнения ниже, кроме как последнее усилие рва, чтобы избежать тупиков. Прочитайте первый комментарий от Стивена Клири.
Быстрое решение здесь . Вместо того чтобы писать:
Пытаться:
Или если вам нужен результат:
Из исходного кода (отредактировано в соответствии с приведенным выше примером):
Для меня это выглядит как полезный вариант, так как у меня нет возможности сделать его асинхронным полностью (что я бы предпочел).
Из источника:
источник
async
кода в ASP.NET и фактически может вызвать проблемы в масштабе. Кстати,ConfigureAwait
не нарушает правильное асинхронное поведение в любом сценарии; это именно то, что вы должны использовать в коде библиотеки.Avoid Exposing Synchronous Wrappers for Asynchronous Implementations
. Вся остальная часть поста объясняет несколько разных способов сделать это, если вам абсолютно необходимо .Поскольку вы используете
.Result
или,.Wait
илиawait
это приведет к тупику в вашем коде.Вы можете использовать
ConfigureAwait(false)
вasync
методах предотвращения тупикакак это:
источник
Эти две школы на самом деле не исключают.
Вот сценарий, где вы просто должны использовать
или что-то вроде
У меня есть действие MVC, которое находится под атрибутом транзакции базы данных. Идея была (вероятно) откатить все, что было сделано в действии, если что-то пойдет не так. Это не позволяет переключение контекста, в противном случае откат транзакции или фиксация само по себе приведет к сбою.
Мне нужна библиотека async, так как ожидается, что она будет работать async.
Единственный вариант. Запустите его как обычный вызов синхронизации.
Я просто говорю каждому свое.
источник
Я собираюсь поместить это здесь больше для полноты, чем для непосредственного отношения к ФП. Я потратил почти день на отладку
HttpClient
запроса, задаваясь вопросом, почему я так и не получил ответ.Наконец обнаружил, что я забыл
await
оasync
вызове дальше вниз по стеку вызовов.Ощущает себя так же хорошо, как пропустить точку с запятой.
источник
Я смотрю здесь:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
И тут:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx
И увидев:
Учитывая, что
await
версия работает, и является ли «правильный» способ ведения дел, вам действительно нужен ответ на этот вопрос?Мой голос: Неправильное использование API .
источник
Test5Controller.Get()
выполните рефакторинг для устранения ожидающего со следующим:var task = AsyncAwait_GetSomeDataAsync(); return task.Result;
То же поведение можно наблюдать.