HttpClient.GetAsync (...) никогда не возвращается при использовании await/async
Edit: этот вопрос выглядит так, как будто это может быть та же проблема, но не имеет ответов...
Edit: в тестовом случае 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();
}
}
5 ответов:
вы злоупотребляете API.
вот такая ситуация: в ASP.NET, только один поток может обрабатывать запрос одновременно. При необходимости можно выполнить некоторую параллельную обработку (заимствование дополнительных потоков из пула потоков), но только один поток будет иметь контекст запроса (дополнительные потоки не имеют контекста запроса).
это управляется ASP.NET
SynchronizationContext
.по умолчанию, когда вы
await
aTask
метод резюме на захваченном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
intro post, который включает в себя краткий описание какTask
ожидающие используютSynchronizationContext
.- The Async / Await FAQ, который более подробно описывает контексты. Также смотрите жду, и UI, и тупики! О, боже!, который тут применить здесь, даже если вы находитесь в ASP.NET вместо пользовательского интерфейса, потому что ASP.NET
SynchronizationContext
ограничивает контекст запроса только одним потоком за раз.- этой сообщение на форуме MSDN.
- Stephen Toub демонстрация этого тупика (с помощью пользовательского интерфейса) и как и Люциан Вищик.
2012-07-13 обновления: включил этот ответ в своем блоге.
Edit: как правило, старайтесь избегать делать ниже, за исключением последнего усилия канавы, чтобы избежать тупиков. Прочитайте первый комментарий от Стивена Клири.
быстрое исправление от здесь. Вместо того, чтобы писать:
Task tsk = AsyncOperation(); tsk.Wait();
попробуй:
Task.Run(() => AsyncOperation()).Wait();
или, если вам нужен результат:
var result = Task.Run(() => AsyncOperation()).Result;
из источника (отредактировано в соответствии с приведенным выше примером):
AsyncOperation теперь будет вызван на ThreadPool, где есть не будет SynchronizationContext и продолжения, используемые внутри AsyncOperation не будет принудительно возвращен к вызывающему потоку.
для меня это выглядит как полезный вариант, так как у меня нет возможности сделать его асинхронным полностью (что я бы предпочел).
источник
убедитесь, что await в методе FooAsync не находит контекст для маршал вернулся. Самый простой способ сделать это-вызвать асинхронная работа с ThreadPool, например, путем обертывания вызов в задаче.Бегите, например
int Sync() { возвращение задачи.Запустить(() => Библиотека.FooAsync()).Результат; }
FooAsync теперь будет вызван на ThreadPool, где не будет a 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, которое находится под атрибутом транзакции базы данных. Идея была (вероятно) откатить все, что сделано в действии, если что-то пойдет не так. Это не позволяет переключать контекст, в противном случае откат транзакции или фиксация завершится неудачей.
библиотека мне нужна асинхронный, как ожидается, чтобы запустить асинхронный.
единственный вариант. Запустите его как обычный вызов синхронизации.
Я просто говорю каждому свое.
Я смотрю тут:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs. 110).aspx
и здесь:
и видим:
этот тип и его членов, предназначены для использования компилятором.
учитывая
await
версия работает, и это "правильный" способ делать вещи, вам действительно нужен ответ на этот вопрос?мой голос: неправильное использование API-интерфейс.