without - try catch await async c#




HttpClient.GetAsync(...) ne retourne jamais lors de l'utilisation de wait/async (4)

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

Edit: dans le WaitingForActivation test 5, la tâche semble être bloquée dans l'état WaitingForActivation .

J'ai rencontré un comportement étrange en utilisant le System.Net.Http.HttpClient dans .NET 4.5 - où "attendant" le résultat d'un appel à (par exemple) httpClient.GetAsync(...) ne retournera jamais.

Cela ne se produit que dans certaines circonstances lorsque vous utilisez la nouvelle fonctionnalité de langage async / await et l'API Tasks - 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) à l'exception de /api/test5 qui ne se termine jamais.

Est-ce que j'ai rencontré un bogue dans la classe HttpClient ou est-ce que j'utilise 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();
    }
}

Ces deux écoles n'excluent pas vraiment.

Voici le scénario où vous devez simplement utiliser

   Task.Run(() => AsyncOperation()).Wait(); 

ou quelque chose comme

   AsyncContext.Run(AsyncOperation);

J'ai une action MVC qui est 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 si quelque chose tournait mal. Cela ne permet pas le changement de contexte, sinon l'annulation ou la validation de la transaction va échouer elle-même.

La bibliothèque dont j'ai besoin est asynchrone car elle devrait fonctionner en mode asynchrone.

La seule option. Exécutez-le comme un appel de synchronisation normal.

Je dis juste à chacun son propre.


Edit: Essayez généralement d'éviter de faire ce qui suit, sauf en dernier recours pour éviter les blocages. Lisez le premier commentaire de Stephen Cleary.

Solution rapide d' here . Au lieu d'écrire:

Task tsk = AsyncOperation();
tsk.Wait();

Essayer:

Task.Run(() => AsyncOperation()).Wait();

Ou si vous avez besoin d'un résultat:

var result = Task.Run(() => AsyncOperation()).Result;

De la source (édité pour correspondre à l'exemple ci-dessus):

AsyncOperation va maintenant être appelé sur le ThreadPool, où il n'y aura pas de SynchronizationContext, et les suites utilisées dans AsyncOperation ne seront pas forcées à revenir au thread appelant.

Pour moi, cela ressemble à une option utilisable puisque je n'ai pas la possibilité de la faire async tout le chemin (ce que je préférerais).

De la source:

Assurez-vous que l'attente dans la méthode FooAsync ne trouve pas de contexte vers lequel revenir. La méthode la plus simple consiste à invoquer le travail asynchrone à partir de ThreadPool, par exemple en enveloppant l'appel dans un Task.Run, par exemple

int Sync () {Renvoie Task.Run (() => Library.FooAsync ()). Résultat; }

FooAsync va maintenant être appelé sur le ThreadPool, où il n'y aura pas de SynchronizationContext, et les suites utilisées à l'intérieur de FooAsync ne seront pas forcées à revenir au thread qui appelle Sync ().


Puisque vous utilisez .Result ou .Wait ou await cela finira par provoquer un blocage dans votre code.

vous pouvez utiliser ConfigureAwait(false) dans les méthodes async pour éviter les interblocages

comme ça:

var result = await httpClient.GetAsync("http://.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);

vous pouvez utiliser ConfigureAwait(false) mesure du possible pour Ne pas bloquer le code asynchrone.


Vous utilisez abusivement l'API.

Voici la situation: dans ASP.NET, un seul thread peut gérer une requête à la fois. Vous pouvez effectuer un traitement parallèle si nécessaire (en empruntant des threads supplémentaires à partir du pool de threads), mais un seul thread aura le contexte de la requête (les threads supplémentaires n'ont pas le contexte de la requête).

Ceci est géré par le SynchronizationContext ASP.NET .

Par défaut, lorsque vous await une Task , la méthode reprend sur un SynchronizationContext capturé (ou un TaskScheduler capturé, s'il n'y a pas de SynchronizationContext ). Normalement, c'est exactement ce que vous voulez: une action de contrôleur asynchrone await quelque chose, et quand elle reprend, elle reprend avec le contexte de la requête.

Alors, voici pourquoi échoue test5 :

  • Test5Controller.Get exécute AsyncAwait_GetSomeDataAsync (dans le contexte de requête ASP.NET).
  • AsyncAwait_GetSomeDataAsync exécute HttpClient.GetAsync (dans le contexte de requête ASP.NET).
  • La requête HTTP est envoyée et HttpClient.GetAsync renvoie une Task inachevée.
  • AsyncAwait_GetSomeDataAsync attend la Task ; car il n'est pas complet, AsyncAwait_GetSomeDataAsync renvoie une Task inachevée.
  • Test5Controller.Get bloque le thread en cours jusqu'à ce que cette Task termine.
  • La réponse HTTP entre et la Task renvoyée par HttpClient.GetAsync est terminée.
  • AsyncAwait_GetSomeDataAsync tente de reprendre dans le contexte de requête ASP.NET. Cependant, il existe déjà un thread dans ce contexte: le thread bloqué dans Test5Controller.Get .
  • Impasse.

Voici pourquoi les autres fonctionnent:

  • ( test1 , test2 et test3 ): Continuations_GetSomeDataAsync programme la continuation vers le pool de threads, en dehors du contexte de requête ASP.NET. Cela permet à la Task renvoyée par Continuations_GetSomeDataAsync de se terminer sans devoir entrer à nouveau dans le contexte de la demande.
  • ( test6 et test6 ): Étant donné que la Task est attendue , le thread de demande ASP.NET n'est pas bloqué. Cela permet à AsyncAwait_GetSomeDataAsync d'utiliser le contexte de requête ASP.NET lorsqu'il est prêt à continuer.

Et voici les meilleures pratiques:

  1. Dans vos méthodes async "library", utilisez ConfigureAwait(false) autant que possible. Dans votre cas, AsyncAwait_GetSomeDataAsync var result = await httpClient.GetAsync("http://.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. Ne bloquez pas sur les Task s; c'est async tout le long. En d'autres termes, utilisez GetResult au lieu de GetResult ( Task.Result et Task.Wait doivent également être remplacés par await ).

De cette façon, vous obtenez les deux avantages: la continuation (le reste de la méthode AsyncAwait_GetSomeDataAsync ) est exécutée sur un thread de base de threads de base qui n'a pas à entrer dans le contexte de requête ASP.NET; et le contrôleur lui-même est async (qui ne bloque pas un thread de demande).

Plus d'information:

Mise à jour 2012-07-13: Intégration de cette réponse dans un article de blog .







dotnet-httpclient