Асинхронний дзвінок із очікуванням у HttpClient ніколи не повертається


95

У мене є дзвінок, який я роблю зсередини C#програми на основі xaml , метро на Win8 CP; цей дзвінок просто потрапляє на веб-службу і повертає дані JSON.

HttpMessageHandler handler = new HttpClientHandler();

HttpClient httpClient = new HttpClient(handler);
httpClient.BaseAddress = new Uri("http://192.168.1.101/api/");

var result = await httpClient.GetStreamAsync("weeklyplan");
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(WeeklyPlanData[]));
return (WeeklyPlanData[])ser.ReadObject(result);

Він зависає на, awaitале виклик http фактично повертається майже негайно (підтверджено через скрипт); це наче awaitігнорується, і воно просто там зависає.

Перш ніж запитати - ТАК - функцію приватної мережі ввімкнено.

Будь-які ідеї, чому це повисне?


1
Як ви називаєте цей asyncметод? Хіба це не створює винятку?
svick

Відповіді:


136

Перевірте цю відповідь на моє запитання, яке, схоже, дуже схоже.

Щось спробувати: зателефонувати ConfigureAwait(false)за Завданням, повернутим до GetStreamAsync(). Напр

var result = await httpClient.GetStreamAsync("weeklyplan")
                             .ConfigureAwait(continueOnCapturedContext:false);

Чи корисно це чи ні, залежить від того, як називається ваш код вище - у моєму випадку виклик asyncметоду за допомогою Task.GetAwaiter().GetResult()призвів до зависання коду.

Це пов’язано з тим, що GetResult()блокує поточний потік до завершення Завдання. Коли завдання завершується, воно намагається повторно ввести контекст потоку, в якому воно було запущено, але не може, оскільки в цьому контексті вже є потік, який заблокований викликом GetResult()... тупику!

У цьому дописі MSDN йдеться про детальну інформацію про те, як .NET синхронізує паралельні потоки - і відповідь на моє власне запитання дає кілька найкращих практик.


12
Дякую, майже відмовився від async / await, перш ніж побачити це.
Den

4
Я також! Чому це не краще документовано? Ще раз спасибі
Avrohom Yisroel

1
Це відбудеться, якщо це не в контексті інтерфейсу користувача та контексті ASP.NET?
машинарій

1
Чудова відповідь! Але мене бентежить, чому дотепер у мене ця проблема виникає лише при використанні HttpClient, здається, що основна реалізація в HttpClient не реалізована правильно. Інші обхідні шляхи, які я знайшов, включають встановлення поточного потоку як STA, що допомагає, але насправді є непрямим, особливо коли ви використовуєте сторонні збори і не знаєте, що під капотом якийсь дзвінок точно буде чекати відповіді, що він ніколи не збирається отримати. У моєму випадку dll була власною, тому ми змогли ConfigureAwait ... але це потрібно було зробити на найнижчому рівні до HttpClient obj.
Кріс Шаллер,

2
@ChrisSchaller Не забудьте прочитати повну відповідь на stackoverflow.com/a/10351400/174735 , де пояснюється проблема досить повно.
Бенджамін Фокс,

5

Попередження - якщо ви пропустите очікування на найвищому рівні в контролері ASP.NET і повернете завдання замість результату як відповідь, воно насправді просто зависає у вкладених викликах очікування без помилок. Дурна помилка, але якби я побачив цю публікацію, це могло б заощадити мені трохи часу на перевірку коду на щось дивне.


0

Застереження: Мені не подобається рішення ConfigureAwait (), оскільки я вважаю його неінтуїтивним і важким для запам'ятовування. Натомість я дійшов висновку, що в Task.Run (() => myAsyncMethodNotUsingAwait ()) буде завершено не очікувані виклики методів. Здається, це працює на 100%, але може бути просто умовою перегонів !? Я не настільки впевнений, що відбувається, чесно кажучи. Цей висновок може бути помилковим, і я ризикую своїми пунктами StackOverflow тут, сподіваючись, навчитися з коментарів :-P. Будь ласка, прочитайте їх!

Я просто мав проблему, як описано, і знайшов тут більше інформації .

Висловлювання: "ви не можете викликати асинхронний метод"

await asyncmethod2()

від методу, який блокує

myAsyncMethod().Result

У моєму випадку я не міг змінити метод виклику, і він не був асинхронним. Але насправді мені було байдуже про результат. Наскільки я пам’ятаю, це також не спрацювало з видаленням .Result і зникнення очікувань.

Отже, я зробив це:

public void Configure()
{
    var data = "my data";
    Task.Run(() => NotifyApi(data));
}

private async Task NotifyApi(bool data)
{
    var toSend = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
    await client.PostAsync("http://...", data);
}

У моєму випадку мені було байдуже про результат у виклику не-асинхронного методу, але, мабуть, це досить часто у цьому випадку використання. Результат можна використовувати у виклику методу асинхронізації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.