Edit: Cette question semble être le même problème, mais n'a pas de réponses ...
Edit: dans le cas de test 5, la tâche semble bloquée WaitingForActivation
.
J'ai rencontré un comportement étrange en utilisant System.Net.Http.HttpClient dans .NET 4.5 - où "attendre" le résultat d'un appel à (par exemple) httpClient.GetAsync(...)
ne reviendra jamais.
Cela ne se produit que dans certaines circonstances lors de l'utilisation de la nouvelle fonctionnalité de langage asynchrone / attente et de l'API Tâches - le code semble toujours fonctionner lorsque vous utilisez uniquement des continuations.
Voici un code qui reproduit le problème - déposez-le dans un nouveau "projet MVC 4 WebApi" dans Visual Studio 11 pour exposer les points de terminaison GET suivants:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
Chacun des points de terminaison renvoie ici les mêmes données (les en-têtes de réponse de stackoverflow.com), sauf pour ceux /api/test5
qui ne se terminent jamais.
Ai-je rencontré un bogue dans la classe HttpClient ou suis-je en train de mal utiliser l'API d'une manière ou d'une autre?
Code à reproduire:
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();
}
}
la source
HttpClient.GetAsync(...)
?Réponses:
Vous utilisez abusivement l'API.
Voici la situation: dans ASP.NET, un seul thread peut gérer une demande à la fois. Vous pouvez effectuer un traitement parallèle si nécessaire (emprunter des threads supplémentaires au pool de threads), mais un seul thread aurait le contexte de la demande (les threads supplémentaires n'ont pas le contexte de la demande).
Ceci est géré par ASP.NET
SynchronizationContext
.Par défaut, lorsque vous
await
aTask
, la méthode reprend sur une captureSynchronizationContext
(ou une captureTaskScheduler
, s'il n'y en a pasSynchronizationContext
). Normalement, c'est exactement ce que vous voulez: une action de contrôleur asynchrone feraawait
quelque chose, et quand elle reprendra, elle reprendra avec le contexte de la demande.Alors, voici pourquoi
test5
échoue:Test5Controller.Get
s'exécuteAsyncAwait_GetSomeDataAsync
(dans le contexte de la demande ASP.NET).AsyncAwait_GetSomeDataAsync
s'exécuteHttpClient.GetAsync
(dans le contexte de la demande ASP.NET).HttpClient.GetAsync
renvoie une requête incomplèteTask
.AsyncAwait_GetSomeDataAsync
attend leTask
; car il n'est pas complet,AsyncAwait_GetSomeDataAsync
renvoie un inachevéTask
.Test5Controller.Get
bloque le thread actuel jusqu'à ce qu'il seTask
termine.Task
retourné parHttpClient.GetAsync
est terminé.AsyncAwait_GetSomeDataAsync
tente de reprendre dans le contexte de la demande ASP.NET. Cependant, il existe déjà un thread dans ce contexte: le thread bloquéTest5Controller.Get
.Voici pourquoi les autres fonctionnent:
test1
,,test2
ettest3
):Continuations_GetSomeDataAsync
planifie la continuation vers le pool de threads, en dehors du contexte de demande ASP.NET. Cela permet auTask
retourné deContinuations_GetSomeDataAsync
se terminer sans avoir à ressaisir le contexte de la demande.test4
ettest6
): Étant donné que leTask
est attendu , le thread de demande ASP.NET n'est pas bloqué. Cela permetAsyncAwait_GetSomeDataAsync
d'utiliser le contexte de demande ASP.NET lorsqu'il est prêt à continuer.Et voici les meilleures pratiques:
async
méthodes de "bibliothèque" , utilisezConfigureAwait(false)
autant que possible. Dans votre cas, cela changeraitAsyncAwait_GetSomeDataAsync
pour êtrevar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Task
s; c'estasync
tout en bas. En d'autres termes, utilisezawait
plutôt queGetResult
(Task.Result
etTask.Wait
devrait également être remplacé parawait
).De cette façon, vous obtenez les deux avantages: la suite (le reste de la
AsyncAwait_GetSomeDataAsync
méthode) est exécutée sur un thread de pool de threads de base qui n'a pas à entrer dans le contexte de la demande ASP.NET; et le contrôleur lui-même estasync
(qui ne bloque pas un thread de demande).Plus d'information:
async
/await
intro , qui comprend une brève description de l'Task
utilisation des serveursSynchronizationContext
.SynchronizationContext
limite le contexte de la demande à un seul fil à la fois.Mise à jour 2012-07-13: incorporation de cette réponse dans un article de blog .
la source
SynchroniztaionContext
qui explique qu'il ne peut y avoir qu'un seul thread dans le contexte pour une demande? Sinon, je pense qu'il devrait y en avoir.SynchronizationContext
fournit quelques fonctionnalités importantes: il coule le contexte de la demande. Cela inclut toutes sortes de choses, de l'authentification aux cookies en passant par la culture. Ainsi, dans ASP.NET, au lieu de synchroniser à nouveau avec l'interface utilisateur, vous effectuez une synchronisation avec le contexte de la demande. Cela peut changer sous peu: le nouveauApiController
a unHttpRequestMessage
contexte en tant que propriété - il n'est donc peut- être pas nécessaire de le faire circulerSynchronizationContext
- mais je ne sais pas encore.Edit: essayez généralement d'éviter de faire ce qui suit, sauf comme un dernier effort pour éviter les blocages. Lisez le premier commentaire de Stephen Cleary.
Solution rapide à partir d' ici . Au lieu d'écrire:
Essayer:
Ou si vous avez besoin d'un résultat:
De la source (édité pour correspondre à l'exemple ci-dessus):
Pour moi, cela ressemble à une option utilisable car je n'ai pas la possibilité de le faire asynchroniser complètement (ce que je préférerais).
De la source:
la source
async
code sur ASP.NET et peut en fait causer des problèmes à grande échelle. BTW,ConfigureAwait
ne "casse pas le comportement asynchrone approprié" dans aucun scénario; c'est exactement ce que vous devez utiliser dans le code de la bibliothèque.Avoid Exposing Synchronous Wrappers for Asynchronous Implementations
. Le reste du message explique plusieurs façons de le faire si vous en avez absolument besoin .Puisque vous utilisez
.Result
ou.Wait
ouawait
cela finira par provoquer un blocage dans votre code.vous pouvez utiliser
ConfigureAwait(false)
dans lesasync
méthodes pour empêcher l'impassecomme ça:
la source
Ces deux écoles n'excluent pas vraiment.
Voici le scénario où vous devez simplement utiliser
ou quelque chose comme
J'ai une action MVC sous l'attribut de transaction de base de données. L'idée était (probablement) de faire reculer tout ce qui se passait dans l'action en cas de problème. Cela ne permet pas le changement de contexte, sinon la restauration ou la validation de transaction échouera d'elle-même.
La bibliothèque dont j'ai besoin est asynchrone car elle devrait s'exécuter asynchrone.
La seule option. Exécutez-le comme un appel de synchronisation normal.
Je dis juste à chacun son propre.
la source
Je vais mettre cela ici pour plus d'exhaustivité que de pertinence directe pour le PO. J'ai passé près d'une journée à déboguer un
HttpClient
demande, me demandant pourquoi je n'obtenais jamais de réponse.Enfin trouvé que j'avais oublié de
await
laasync
appel plus bas dans la pile des appels.Ça fait presque comme manquer un point-virgule.
la source
Je regarde ici:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
Et ici:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx
Et en voyant:
Étant donné que la
await
version fonctionne et est la «bonne» façon de faire les choses, avez-vous vraiment besoin d'une réponse à cette question?Mon vote est: abuser de l'API .
la source
Test5Controller.Get()
pour éliminer l'attente avec ce qui suit:var task = AsyncAwait_GetSomeDataAsync(); return task.Result;
Le même comportement peut être observé.