Асинхронный вызов с ожиданием в 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игнорируется и просто висит там.

Прежде чем вы спросите - ДА - возможность частной сети включена.

Есть идеи, почему это зависло?

keithwarren7
источник
1
Как вы называете этот asyncметод? Разве это не вызывает исключение?
svick

Ответы:

136

Посмотрите этот ответ на мой вопрос, который кажется очень похожим.

Что можно попробовать: вызовите ConfigureAwait(false)Задачу, возвращенную GetStreamAsync(). Например

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

Полезно это или нет, зависит от того, как вызывается приведенный выше код - в моем случае вызов asyncметода using Task.GetAwaiter().GetResult()привел к зависанию кода.

Это связано с тем, GetResult()что текущий поток блокируется до завершения задачи. Когда задача завершается, она пытается повторно войти в контекст потока, в котором она была запущена, но не может, потому что в этом контексте уже есть поток, который заблокирован вызовом GetResult()... взаимоблокировки!

В этом сообщении MSDN подробно рассказывается о том, как .NET синхронизирует параллельные потоки, а ответ на мой собственный вопрос дает некоторые передовые практики.

Бенджамин Фокс
источник
12
Спасибо, почти отказался от async / await, прежде чем увидел это.
Den
4
Я тоже! Почему это не документировано лучше? Еще раз спасибо
Avrohom Yisroel
1
Произойдет ли это, если это не произойдет ни в контексте пользовательского интерфейса, ни в контексте ASP.NET?
Machinarium
1
Отличный ответ! Но я не понимаю, почему до сих пор у меня эта проблема возникает только при использовании HttpClient, похоже, что основная реализация в HttpClient не реализована правильно. Другие обходные пути, которые я нашел, включают установку текущего потока как STA, что помогает, но действительно косвенно, особенно когда вы используете стороннюю сборку и не знаете, что под капотом какой-то вызов будет определенно ждать ответа, что он никогда не получится. В моем случае dll была внутренней, поэтому мы смогли ConfigureAwait ... но это нужно было сделать на самом низком уровне для объекта HttpClient.
Крис Шаллер,
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);
}

В моем случае меня не волновал результат вызова неасинхронного метода, но я предполагаю, что это довольно часто встречается в этом случае использования. Вы можете использовать результат в вызывающем методе async.

CodingYourLife
источник