364 votes

HttpClient.GetAsync(...) ne retourne jamais lorsqu'on utilise await/async

Modifier: Cette question semble être le même problème, mais n'a pas de réponses...

Modifier: Dans le cas de test 5, la tâche semble être bloquée dans l'état 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 renverra jamais.

Cela ne se produit que dans certaines circonstances lors de l'utilisation de la nouvelle fonctionnalité de langage async/await et de l'API Tasks - le code semble toujours fonctionner lorsque seules les continuations sont utilisées.

Voici un code qui reproduit le problème - ajoutez ceci dans un nouveau "projet WebApi MVC 4" dans Visual Studio 11 pour exposer les points d'extrémité GET suivants :

/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- ne se termine jamais
/api/test6

Chacun des points d'extrémité ici renvoie les mêmes données (les en-têtes de réponse de stackoverflow.com) sauf /api/test5 qui ne se termine jamais.

Ai-je rencontré un bug dans la classe HttpClient, ou est-ce que j'utilise mal l'API d'une manière ou d'une autre ?

Code pour reproduire :

public class BaseApiController : ApiController
{
    /// 
    /// Récupère des données en utilisant des continuations
    /// 
    protected Task Continuations_GetSomeDataAsync()
    {
        var httpClient = new HttpClient();

        var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);

        return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
    }

    /// 
    /// Récupère des données en utilisant async/await
    /// 
    protected async Task 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
{
    /// 
    /// Gère la tâche en utilisant Async/Await
    /// 
    public async Task Get()
    {
        var data = await Continuations_GetSomeDataAsync();

        return data;
    }
}

public class Test2Controller : BaseApiController
{
    /// 
    /// Gère la tâche en bloquant le thread jusqu'à ce que la tâche se termine
    /// 
    public string Get()
    {
        var task = Continuations_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test3Controller : BaseApiController
{
    /// 
    /// Passe la tâche au contrôleur hôte
    /// 
    public Task Get()
    {
        return Continuations_GetSomeDataAsync();
    }
}

public class Test4Controller : BaseApiController
{
    /// 
    /// Gère la tâche en utilisant Async/Await
    /// 
    public async Task Get()
    {
        var data = await AsyncAwait_GetSomeDataAsync();

        return data;
    }
}

public class Test5Controller : BaseApiController
{
    /// 
    /// Gère la tâche en bloquant le thread jusqu'à ce que la tâche se termine
    /// 
    public string Get()
    {
        var task = AsyncAwait_GetSomeDataAsync();

        var data = task.GetAwaiter().GetResult();

        return data;
    }
}

public class Test6Controller : BaseApiController
{
    /// 
    /// Passe la tâche au contrôleur hôte
    /// 
    public Task Get()
    {
        return AsyncAwait_GetSomeDataAsync();
    }
}

2 votes

Il ne semble pas que ce soit le même problème, mais juste pour vous informer, il y a un bug MVC4 dans la version bêta concernant les méthodes asynchrones qui se terminent de manière synchrone - voir stackoverflow.com/questions/9627329/…

0 votes

Merci - je vais faire attention à cela. Dans ce cas, je pense que la méthode devrait toujours être asynchrone en raison de l'appel à HttpClient.GetAsync(...)?

2voto

Alexey Podlasov Points 163

Dans mon cas, 'await' ne s'est jamais terminé en raison d'une exception lors de l'exécution de la requête, par exemple, le serveur ne répond pas, etc. Entourez-le de try..catch pour identifier ce qui s'est passé, cela complétera également votre 'await' de manière élégante.

public async Task GetStuff(string id)
{
    string path = $"/api/v2/stuff/{id}";
    try
    {
        HttpResponseMessage response = await client.GetAsync(path);
        if (response.StatusCode == HttpStatusCode.OK)
        {
            string json = await response.Content.ReadAsStringAsync();
            return JsonUtility.FromJson(json);
        }
        else
        {
            Debug.LogError($"Impossible de récupérer les données {id}");
        }
    }
    catch (Exception exception)
    {
        Debug.LogError($"Exception lors de la récupération des données {exception}");
    }
    return null;
}

0 votes

Ceci est une réponse négligée. Parfois, l'erreur n'est pas apparente. Par exemple, si vous initialisez une variable dans OnIntialised le moteur de rendu peut renvoyer une erreur s'il essayait de construire à partir de cette variable, car le thread est toujours en attente, puis tout s'effondre sans aucune erreur. Cela a répondu à un problème sur lequel je travaillais depuis des semaines.

2voto

Shahid Islam Points 350

J'utilisais trop de await, donc je ne recevais pas de réponse, j'ai converti l'appel en synchronisé et ça a commencé à fonctionner

            using (var client = new HttpClient())
            using (var request = new HttpRequestMessage())
            {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                request.Method = HttpMethod.Get;
                request.RequestUri = new Uri(URL);
                var response = client.GetAsync(URL).Result;
                response.EnsureSuccessStatusCode();
                string responseBody = response.Content.ReadAsStringAsync().Result;

0 votes

La variable 'request' ne semble pas être utilisée ?

0voto

yamen Points 9976

Je regarde ici :

http://msdn.microsoft.com/fr-fr/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx

Et ici :

http://msdn.microsoft.com/fr-fr/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx

Et en voyant :

Ce type et ses membres sont destinés à être utilisés par le compilateur.

Considérant que la version await fonctionne, et est la bonne manière de faire les choses, avez-vous vraiment besoin d'une réponse à cette question?

Mon vote est : Utilisation incorrecte de l'API.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X