С помощью HttpClient.GetAsync (...) никогда не возвращается при использовании await/async
Edit: этот вопрос похоже, это может быть та же проблема, но нет ответов...
Edit: в тестовом случае 5 Задача, похоже, застряла в WaitingForActivation
государство.
я столкнулся с некоторым странным поведением, используя систему.Сеть.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();
}
}
5 ответов
вы злоупотребляете API.
вот ситуация: в ASP.NET, только один поток может обрабатывать запрос за раз. При необходимости можно выполнить некоторую параллельную обработку (заимствование дополнительных потоков из пула потоков), но только один поток будет иметь контекст запроса (дополнительные потоки не имеют контекста запроса).
это управляется ASP.NET SynchronizationContext
.
по умолчанию, когда вы await
a Task
метод резюме по захваченному SynchronizationContext
(или в плен TaskScheduler
, если не SynchronizationContext
). Обычно это именно то, что вы хотите: действие асинхронного контроллера будет await
что-то, и когда он возобновляется, он возобновляется с контекстом запроса.
вот почему test5
не удается:
-
Test5Controller.Get
выполняетAsyncAwait_GetSomeDataAsync
(в пределах ASP.NET контекст запроса). -
AsyncAwait_GetSomeDataAsync
выполняетHttpClient.GetAsync
(в пределах ASP.NET контекст запроса). - протокола HTTP запрос разослан, и
HttpClient.GetAsync
возвращает неисполненныйTask
. -
AsyncAwait_GetSomeDataAsync
ждетTask
; поскольку он не полный,AsyncAwait_GetSomeDataAsync
возвращает неисполненныйTask
. -
Test5Controller.Get
блоки текущий поток до этогоTask
завершается. - приходит HTTP-ответ, и
Task
возвращено это. -
AsyncAwait_GetSomeDataAsync
попытки возобновить внутри ASP.NET контекст запроса. Тем не менее, уже есть поток в этом контексте: поток заблокирован вTest5Controller.Get
. - тупик.
вот почему работают другие:
- (
test1
,test2
иtest3
):Continuations_GetSomeDataAsync
планирует продолжение в пул потоков,за пределами ASP.NET контекст запроса. Это позволяетTask
возвращеноContinuations_GetSomeDataAsync
для завершения без необходимости повторного ввода контекста запроса. - (
test4
иtest6
): СTask
is ждал, ASP.NET поток запроса не блокируется. Это позволяетAsyncAwait_GetSomeDataAsync
использовать ASP.NET запросить контекст, когда он будет готов продолжить.
и вот лучшие практики:
- в вашей "библиотеке"
async
методы, использоватьConfigureAwait(false)
всякий раз, когда это возможно. В вашем случае это изменитсяAsyncAwait_GetSomeDataAsync
наvar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
- не блокировать на
Task
s; этоasync
полностью вниз. Другими словами, используйтеawait
вместоGetResult
(Task.Result
иTask.Wait
также следует заменитьawait
).
таким образом, вы получаете оба преимущества: продолжение (остаток AsyncAwait_GetSomeDataAsync
метод) выполняется в основном потоке пула потоков, который не должен вводить ASP.NET контекст запроса; и сам контроллер async
(который не блокирует поток запросов).
дополнительная информация:
- мой
async
/await
интро в должности, который включает в себя краткий описание какTask
использовать awaitersSynchronizationContext
. - на async / Await FAQ, который идет более подробно о контекстах. Также смотрите ждать, и UI, и тупики! Боже мой!, который тут применить здесь, даже если вы находитесь в ASP.NET вместо пользовательского интерфейса, потому что ASP.NET
SynchronizationContext
ограничивает контекст запроса только одним потоком за раз. - этой сообщение форума MSDN.
- Стивен Toub Демос этот тупик (с помощью пользовательского интерфейса) и так же Лукиан Wischik.
2012-07-13 обновления: включил этот ответ в своем блоге.
правка: вообще стараюсь не делать, кроме как последней отчаянной попытке избежать взаимоблокировок. Прочтите первый комментарий Стивена Клири.
быстрое исправление от здесь. Вместо того, чтобы писать:
Task tsk = AsyncOperation();
tsk.Wait();
попробуй:
Task.Run(() => AsyncOperation()).Wait();
или, если вам нужен результат:
var result = Task.Run(() => AsyncOperation()).Result;
из источника (отредактировано в соответствии с приведенным выше примером):
AsyncOperation теперь будет вызываться в ThreadPool, где есть не будет SynchronizationContext и продолжения, используемые внутри AsyncOperation не будет принудительно возвращен к вызывающему потоку.
для меня это выглядит как полезный вариант, так как у меня нет возможности сделать его асинхронным полностью (что я бы предпочел).
источник
убедитесь, что await в методе FooAsync не находит контекста для маршал вернулся. Самый простой способ сделать это-вызвать асинхронная работа с ThreadPool, как путем оборачивать вызов в задаче.Запустить, например,
синхронизация int() { задача возврата.Выполнить (() = > Библиотека.FooAsync()).Результат; }
FooAsync теперь будет вызываться в ThreadPool, где не будет SynchronizationContext и продолжения, используемые внутри FooAsync не будет принудительно возвращен в поток, вызывающий Sync ().
если вы используете .Result
или .Wait
или await
это в конечном итоге вызовет тупик в коде.
можно использовать ConfigureAwait(false)
на async
методы предотвращение взаимоблокировки
такой:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
можно использовать
ConfigureAwait(false)
везде, где это возможно, не блокируйте асинхронный код .
эти две школы на самом деле не исключают.
вот сценарий, в котором вам просто нужно использовать
Task.Run(() => AsyncOperation()).Wait();
или что-то вроде
AsyncContext.Run(AsyncOperation);
у меня есть действие MVC, которое находится под атрибутом транзакции базы данных. Идея заключалась (вероятно) в том, чтобы откатить все, что сделано в действии, если что-то пойдет не так. Это не позволяет переключать контекст, иначе откат транзакции или фиксация будут завершаться с ошибкой.
библиотека мне нужна async, поскольку ожидается, что он будет работать асинхронно.
единственный вариант. Запустите его как обычный вызов синхронизации.
Я просто говорю каждому свое.
Я смотрю тут:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs. 110).aspx
и здесь:
и видим:
этот тип и его членов, предназначены для использования компилятором.
учитывая await
версия работает, и это "правильный" способ делать вещи, вам действительно нужен ответ на этот вопрос?
мой голос: неправильное использование API.