c# - without - HttpClient.GetAsync(...) kehrt nie zurück, wenn "Warten/Asynchron" verwendet wird




c# await inline (4)

Bearbeiten: Diese Frage sieht aus wie das gleiche Problem, aber hat keine Antworten ...

Edit: Im Testfall 5 scheint die Aufgabe im WaitingForActivation zu WaitingForActivation .

Ich habe einige seltsame Verhalten mit dem System.Net.Http.HttpClient in .NET 4.5 aufgetreten - wo "warten" auf das Ergebnis eines Aufrufs an (zB) httpClient.GetAsync(...) wird nie zurückkehren.

Dies tritt nur unter bestimmten Umständen auf, wenn die neue async / appear-Sprachfunktionalität und die Tasks-API verwendet werden - der Code scheint immer zu funktionieren, wenn nur Fortsetzungen verwendet werden.

Hier ist ein Code, der das Problem reproduziert: Legen Sie dieses in einem neuen "MVC 4 WebApi-Projekt" in Visual Studio 11 ab, um die folgenden GET-Endpunkte verfügbar zu machen:

/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6

Jeder der Endpunkte gibt die gleichen Daten zurück (die Antwortheader von stackoverflow.com), mit Ausnahme von /api/test5 die niemals abgeschlossen wird.

Bin ich in der HttpClient-Klasse auf einen Fehler gestoßen oder missbrauche ich die API irgendwie?

Code zum Reproduzieren:

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

https://code.i-harness.com


Da Sie .Result oder .Wait oder await dies zu einem Deadlock in Ihrem Code.

Sie können ConfigureAwait(false) in async Methoden verwenden, um Deadlocks zu verhindern

so was:

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

Sie können ConfigureAwait(false) wo immer möglich verwenden, um Async-Code nicht zu blockieren.


Diese beiden Schulen schließen nicht wirklich aus.

Hier ist das Szenario, wo Sie einfach verwenden müssen

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

oder so ähnlich

   AsyncContext.Run(AsyncOperation);

Ich habe eine MVC-Aktion, die unter Datenbank Transaktionsattribut ist. Die Idee war (wahrscheinlich), alles, was in der Aktion gemacht wurde, rückgängig zu machen, wenn etwas schief geht. Dies erlaubt keine Kontextumschaltung, da sonst ein Rollback oder Commit der Transaktion selbst fehlschlägt.

Die Bibliothek, die ich brauche, ist asynchron, da Async erwartet wird.

Die einzige Option. Führen Sie es als normalen Synchronisierungsanruf aus.

Ich sage nur jedem etwas.



Sie missbrauchen die API.

Hier ist die Situation: In ASP.NET kann jeweils nur ein Thread eine Anfrage bearbeiten. Sie können bei Bedarf eine parallele Verarbeitung durchführen (zusätzliche Threads aus dem Thread-Pool leihen), aber nur ein Thread hat den Anforderungskontext (die zusätzlichen Threads haben keinen Anforderungskontext).

Dies wird vom ASP.NET- SynchronizationContext .

Wenn Sie await einen Task await , wird die Methode standardmäßig bei einem erfassten SynchronizationContext (oder einem erfassten TaskScheduler , wenn kein SynchronizationContext ) TaskScheduler . Normalerweise ist dies genau das, was Sie wollen: Eine asynchrone Controller-Aktion await etwas, und wenn es fortgesetzt wird, wird es mit dem Anforderungskontext fortgesetzt.

Also, hier ist warum test5 fehlschlägt:

  • Test5Controller.Get führt AsyncAwait_GetSomeDataAsync (innerhalb des ASP.NET-Anforderungskontexts) aus.
  • AsyncAwait_GetSomeDataAsync führt HttpClient.GetAsync (innerhalb des ASP.NET-Anforderungskontexts) aus.
  • Die HTTP-Anforderung wird gesendet, und HttpClient.GetAsync gibt eine unvollständige Task .
  • AsyncAwait_GetSomeDataAsync erwartet die Task . Da es nicht abgeschlossen ist, gibt AsyncAwait_GetSomeDataAsync eine unvollständige Task .
  • Test5Controller.Get blockiert den aktuellen Thread, bis diese Task abgeschlossen ist.
  • Die HTTP-Antwort kommt herein und die von HttpClient.GetAsync Task ist abgeschlossen.
  • AsyncAwait_GetSomeDataAsync versucht, den ASP.NET-Anforderungskontext AsyncAwait_GetSomeDataAsync . In diesem Kontext befindet sich jedoch bereits ein Thread: der in Test5Controller.Get blockierte Test5Controller.Get .
  • Sackgasse.

Hier ist, warum die anderen arbeiten:

  • ( test1 , test2 und test3 ): Continuations_GetSomeDataAsync plant die Fortsetzung des test3 außerhalb des ASP.NET-Anforderungskontexts. Dadurch kann die von Continuations_GetSomeDataAsync Task abgeschlossen werden, ohne dass der Anfragekontext erneut eingegeben werden muss.
  • ( test4 und test6 ): Da der Task erwartet wird , wird der ASP.NET-Anforderungs-Thread nicht blockiert. Dadurch kann AsyncAwait_GetSomeDataAsync den ASP.NET-Anforderungskontext verwenden, wenn der AsyncAwait_GetSomeDataAsync fortgesetzt werden soll.

Und hier sind die besten Praktiken:

  1. Verwenden Sie in Ihren async "Bibliotheks" -Methoden ConfigureAwait(false) wann immer dies möglich ist. In Ihrem Fall würde dies AsyncAwait_GetSomeDataAsync ändern, um var result = await httpClient.GetAsync("http://.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
  2. Blockiere nicht auf Task s; Es ist async ganzen Weg hinunter. Mit anderen Worten, verwenden Sie anstelle von GetResult await ( Task.Result und Task.Wait sollten auch durch await ).

Auf diese Weise erhalten Sie beide Vorteile: Die Fortsetzung (der Rest der AsyncAwait_GetSomeDataAsync Methode) wird auf einem grundlegenden AsyncAwait_GetSomeDataAsync Thread ausgeführt, der den ASP.NET-Anforderungskontext nicht eingeben muss. und der Controller selbst ist async (was einen Anfrage-Thread nicht blockiert).

Mehr Informationen:

Update 2012-07-13: Diese Antwort wurde in einen Blogpost integriert .





dotnet-httpclient