Chamada assíncrona com await em HttpClient nunca retorna

95

Tenho uma chamada que estou fazendo de dentro de um C#aplicativo metro baseado em xaml no Win8 CP; essa chamada simplesmente atinge um serviço da web e retorna dados 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);

Ele trava no, awaitmas a chamada http na verdade retorna quase imediatamente (confirmado pelo fiddler); é como se o awaitfosse ignorado e simplesmente permanecesse ali.

Antes de perguntar - SIM - o recurso de rede privada está ativado.

Alguma ideia de por que isso iria travar?

keithwarren7
fonte
1
Como você está chamando esse asyncmétodo? Não lança uma exceção?
svick

Respostas:

136

Verifique esta resposta à minha pergunta que parece ser muito semelhante.

Algo para tentar: chame ConfigureAwait(false)a Tarefa retornada por GetStreamAsync(). Por exemplo

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

Se isso é útil ou não, depende de como o código acima está sendo chamado - no meu caso, chamar o asyncmétodo usando Task.GetAwaiter().GetResult()fez com que o código travasse.

Isso ocorre porque GetResult()bloqueia o thread atual até que a tarefa seja concluída. Quando a tarefa é concluída, ela tenta entrar novamente no contexto do encadeamento no qual foi iniciado, mas não consegue porque já existe um encadeamento nesse contexto, que está bloqueado pela chamada para GetResult()... deadlock!

Esta postagem do MSDN apresenta alguns detalhes sobre como o .NET sincroniza threads paralelos - e a resposta dada à minha própria pergunta fornece algumas práticas recomendadas.

Benjamin Fox
fonte
12
Obrigado, quase desisti de async / await antes de ver isso.
Den
4
Eu também! Por que isso não está melhor documentado? Obrigado novamente
Avrohom Yisroel
1
Isso vai acontecer se não estiver no contexto da IU e no contexto do ASP.NET?
machinarium
1
Resposta incrível! Mas estou confuso por que até agora só tenho esse problema ao usar HttpClient, parece que a implementação subjacente em HttpClient não foi implementada corretamente. As outras soluções alternativas que encontrei envolvem definir o encadeamento atual como STA, o que ajuda, mas é realmente indireto, especialmente quando você está usando um assembly de terceiros e não está ciente de que, nos bastidores, alguma chamada vai esperar definitivamente por uma resposta de que nunca vai conseguir. No meu caso, a dll era interna, então pudemos ConfigureAwait ... mas tinha que ser feito no nível mais baixo para o objeto HttpClient.
Chris Schaller
2
@ChrisSchaller Certifique-se de ler a resposta completa em stackoverflow.com/a/10351400/174735 , que explica o problema completamente.
Benjamin Fox
5

Apenas um aviso - se você perder o await no nível superior em um controlador ASP.NET e retornar a tarefa em vez do resultado como uma resposta, ele na verdade trava na (s) chamada (s) await aninhada (s) sem erros. Um erro bobo, mas se eu tivesse visto esta postagem, poderia ter me poupado algum tempo checando o código por algo estranho.

bozzle
fonte
0

Isenção de responsabilidade: não gosto da solução ConfigureAwait () porque a considero não intuitiva e difícil de lembrar. Em vez disso, cheguei à conclusão de envolver chamadas de método não esperadas em Task.Run (() => myAsyncMethodNotUsingAwait ()). Isso parece funcionar 100%, mas pode ser apenas uma condição de corrida !? Não tenho certeza do que está acontecendo para ser honesto. Esta conclusão pode estar errada e arrisco meus pontos StackOverflow aqui para aprender com os comentários :-P. Por favor, leia-os!

Acabei de ter o problema conforme descrito e encontrei mais informações aqui .

A afirmação é: "você não pode chamar um método assíncrono"

await asyncmethod2()

de um método que bloqueia

myAsyncMethod().Result

No meu caso, não consegui alterar o método de chamada e não era assíncrono. Mas eu realmente não me importei com o resultado. Pelo que me lembro também não funcionou remover o .Result e ter o await faltando.

Então eu fiz isso:

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);
}

No meu caso, não me importei com o resultado da chamada do método não assíncrono, mas acho que isso é bastante comum neste caso de uso. Você pode usar o resultado no método assíncrono de chamada.

CodingYourLife
fonte