Edit: Esta pergunta parece que pode ser o mesmo problema, mas não tem respostas ...
Editar: No caso de teste 5, a tarefa parece estar parada no WaitingForActivation
estado.
Eu encontrei um comportamento estranho usando o System.Net.Http.HttpClient no .NET 4.5 - onde "aguardando" o resultado de uma chamada para (por exemplo) httpClient.GetAsync(...)
nunca retornará.
Isso ocorre apenas em determinadas circunstâncias ao usar a nova funcionalidade de idioma assíncrono / aguardado e a API de tarefas - o código sempre parece funcionar ao usar apenas continuações.
Aqui está um código que reproduz o problema - coloque-o em um novo "projeto MVC 4 WebApi" no Visual Studio 11 para expor os seguintes pontos de extremidade GET:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
Cada um dos terminais aqui retorna os mesmos dados (os cabeçalhos de resposta de stackoverflow.com), exceto pelos /api/test5
que nunca são concluídos.
Encontrei um bug na classe HttpClient ou estou usando a API de alguma forma?
Código a reproduzir:
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();
}
}
fonte
HttpClient.GetAsync(...)
?Respostas:
Você está usando mal a API.
Aqui está a situação: no ASP.NET, apenas um thread pode manipular uma solicitação por vez. Você pode executar algum processamento paralelo, se necessário (emprestando encadeamentos adicionais do conjunto de encadeamentos), mas apenas um encadeamento teria o contexto de solicitação (os encadeamentos adicionais não terão o contexto de solicitação).
Isso é gerenciado pelo ASP.NET
SynchronizationContext
.Por padrão, quando você
await
aTask
, o método continua em uma capturaSynchronizationContext
(ou uma capturaTaskScheduler
, se não houverSynchronizationContext
). Normalmente, é exatamente isso que você deseja: uma ação do controlador assíncrona seráawait
algo e, quando retomada, retoma com o contexto da solicitação.Então, aqui está o porquê
test5
falha:Test5Controller.Get
é executadoAsyncAwait_GetSomeDataAsync
(dentro do contexto de solicitação do ASP.NET).AsyncAwait_GetSomeDataAsync
é executadoHttpClient.GetAsync
(dentro do contexto de solicitação do ASP.NET).HttpClient.GetAsync
retorna um incompletoTask
.AsyncAwait_GetSomeDataAsync
aguarda oTask
; como não está completo,AsyncAwait_GetSomeDataAsync
retorna um incompletoTask
.Test5Controller.Get
bloqueia o encadeamento atual até que sejaTask
concluído.Task
retornada porHttpClient.GetAsync
é concluída.AsyncAwait_GetSomeDataAsync
tenta retomar dentro do contexto de solicitação do ASP.NET. No entanto, já existe um segmento nesse contexto: o segmento bloqueadoTest5Controller.Get
.Eis por que os outros funcionam:
test1
,,test2
etest3
):Continuations_GetSomeDataAsync
agenda a continuação para o pool de threads, fora do contexto de solicitação do ASP.NET. Isso permite que oTask
retornadoContinuations_GetSomeDataAsync
seja concluído sem ter que entrar novamente no contexto da solicitação.test4
Etest6
): Desde oTask
é aguardado , o thread de solicitação ASP.NET não está bloqueado. Isso permiteAsyncAwait_GetSomeDataAsync
usar o contexto de solicitação do ASP.NET quando estiver pronto para continuar.E aqui estão as práticas recomendadas:
async
métodos da sua "biblioteca" , useConfigureAwait(false)
sempre que possível. No seu caso, isso mudariaAsyncAwait_GetSomeDataAsync
para servar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Task
s; estáasync
todo o caminho. Em outras palavras, use emawait
vez deGetResult
(Task.Result
eTask.Wait
também deve ser substituído porawait
).Dessa forma, você obtém os dois benefícios: a continuação (o restante do
AsyncAwait_GetSomeDataAsync
método) é executada em um thread básico do pool de threads que não precisa entrar no contexto de solicitação do ASP.NET; e o próprio controlador éasync
(que não bloqueia um encadeamento de solicitação).Mais Informações:
async
/await
introdução pós , que inclui uma descrição breve de comoTask
awaiters usarSynchronizationContext
.SynchronizationContext
restringe o contexto da solicitação a apenas um encadeamento por vez.Atualização 13/07/2012: Incorporou esta resposta em uma postagem do blog .
fonte
SynchroniztaionContext
que explica que pode haver apenas um thread no contexto para alguma solicitação? Se não, acho que deveria haver.SynchronizationContext
fornece algumas funcionalidades importantes: ele flui o contexto da solicitação. Isso inclui todos os tipos de coisas, desde autenticação a cookies e cultura. Portanto, no ASP.NET, em vez de sincronizar novamente com a interface do usuário, você sincroniza novamente com o contexto da solicitação. Isso pode mudar em breve: o novoApiController
tem umHttpRequestMessage
contexto como propriedade - portanto, pode não ser necessário fazer o fluxo fluirSynchronizationContext
- mas ainda não sei.Editar: Geralmente, tente evitar o procedimento abaixo, exceto como um último esforço para evitar conflitos. Leia o primeiro comentário de Stephen Cleary.
Solução rápida a partir daqui . Em vez de escrever:
Experimentar:
Ou se você precisar de um resultado:
Na fonte (editada para corresponder ao exemplo acima):
Para mim, isso parece uma opção utilizável, pois eu não tenho a opção de fazê-lo assíncrono o tempo todo (o que eu preferiria).
Da fonte:
fonte
async
código no ASP.NET e, de fato, pode causar problemas em grande escala. BTW,ConfigureAwait
não "quebra o comportamento assíncrono apropriado" em nenhum cenário; é exatamente o que você deve usar no código da biblioteca.Avoid Exposing Synchronous Wrappers for Asynchronous Implementations
. Todo o restante do post está explicando algumas maneiras diferentes de fazer isso, se você absolutamente precisar .Como você está usando
.Result
ou.Wait
ouawait
isso acabará causando um impasse no seu código.você pode usar
ConfigureAwait(false)
emasync
métodos para evitar conflitocomo isso:
fonte
Essas duas escolas não são realmente excludentes.
Aqui está o cenário em que você simplesmente precisa usar
ou algo parecido
Eu tenho uma ação MVC que está sob o atributo de transação do banco de dados. A idéia era (provavelmente) reverter tudo o que foi feito na ação, se algo der errado. Isso não permite a alternância de contexto; caso contrário, a reversão ou confirmação da transação falhará.
A biblioteca que eu preciso é assíncrona, pois é esperado que ela seja executada.
A única opção. Execute-o como uma chamada de sincronização normal.
Estou apenas dizendo a cada um o seu.
fonte
Vou colocar isso aqui mais por completude do que por relevância direta para o OP. Passei quase um dia depurando uma
HttpClient
solicitação, me perguntando por que nunca recebi uma resposta.Finalmente descobri que eu havia esquecido
await
aasync
chamada mais abaixo na pilha de chamadas.Parece tão bom quanto um ponto e vírgula.
fonte
Estou procurando aqui:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
E aqui:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx
E vendo:
Considerando que a
await
versão funciona, e é a maneira 'certa' de fazer as coisas, você realmente precisa de uma resposta para esta pergunta?Meu voto é: Uso indevido da API .
fonte
Test5Controller.Get()
para eliminar o garçom com o seguinte:var task = AsyncAwait_GetSomeDataAsync(); return task.Result;
O mesmo comportamento pode ser observado.