Bearbeiten: Diese Frage scheint das gleiche Problem zu sein, hat aber keine Antworten ...
Bearbeiten: In Testfall 5 scheint die Aufgabe im WaitingForActivation
Zustand zu stecken .
Ich habe ein seltsames Verhalten bei der Verwendung des System.Net.Http.HttpClient in .NET 4.5 festgestellt, bei dem das "Warten" auf das Ergebnis eines Aufrufs von (z. B.) httpClient.GetAsync(...)
niemals zurückkehrt.
Dies tritt nur unter bestimmten Umständen auf, wenn die neue Sprachfunktionalität async / await 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 diesen 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 hier gibt dieselben Daten zurück (die Antwortheader von stackoverflow.com), außer dass /api/test5
diese niemals abgeschlossen werden.
Habe ich einen Fehler in der HttpClient-Klasse festgestellt oder missbrauche ich die API auf irgendeine Weise?
Zu reproduzierender Code:
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();
}
}
quelle
HttpClient.GetAsync(...)
? Immer asynchron sein sollte .Antworten:
Sie missbrauchen die API.
Hier ist die Situation: In ASP.NET kann jeweils nur ein Thread eine Anforderung verarbeiten. Sie können bei Bedarf eine parallele Verarbeitung durchführen (zusätzliche Threads aus dem Thread-Pool ausleihen), aber nur ein Thread würde den Anforderungskontext haben (die zusätzlichen Threads haben nicht den Anforderungskontext).
Dies wird von ASP.NET verwaltet
SynchronizationContext
.Wenn Sie
await
a verwendenTask
, wird die Methode standardmäßig für eine erfassteSynchronizationContext
(oder eine erfassteTaskScheduler
, wenn keine vorhanden ist) fortgesetztSynchronizationContext
. Normalerweise ist dies genau das, was Sie wollen: Eine asynchrone Controller-Aktion wirdawait
etwas bewirken, und wenn sie fortgesetzt wird, wird sie mit dem Anforderungskontext fortgesetzt.Hier ist der Grund, warum dies
test5
fehlschlägt:Test5Controller.Get
wird ausgeführtAsyncAwait_GetSomeDataAsync
(im ASP.NET-Anforderungskontext).AsyncAwait_GetSomeDataAsync
wird ausgeführtHttpClient.GetAsync
(im ASP.NET-Anforderungskontext).HttpClient.GetAsync
gibt eine unvollständige zurückTask
.AsyncAwait_GetSomeDataAsync
erwartet dasTask
; da es nicht vollständig ist, wirdAsyncAwait_GetSomeDataAsync
eine unvollständige zurückgegebenTask
.Test5Controller.Get
blockiert den aktuellen Thread, bis dieserTask
abgeschlossen ist.Task
Rückgabe vonHttpClient.GetAsync
ist abgeschlossen.AsyncAwait_GetSomeDataAsync
Versuche, im ASP.NET-Anforderungskontext fortzufahren. In diesem Kontext gibt es jedoch bereits einen Thread: Der Thread ist blockiertTest5Controller.Get
.Hier ist, warum die anderen arbeiten:
test1
,,test2
undtest3
):Continuations_GetSomeDataAsync
Plant die Fortsetzung des Thread-Pools außerhalb des ASP.NET-Anforderungskontexts. Dadurch kann dieTask
Rückgabe vonContinuations_GetSomeDataAsync
abgeschlossen werden, ohne dass der Anforderungskontext erneut eingegeben werden muss.test4
Undtest6
): Da dasTask
wird abgewartet , der ASP.NET Anforderungsthread nicht blockiert ist . Auf diese Weise können SieAsyncAwait_GetSomeDataAsync
den ASP.NET-Anforderungskontext verwenden, wenn Sie fortfahren möchten.Und hier sind die Best Practices:
async
Verwenden Sie in Ihren "Bibliotheks" -Methoden,ConfigureAwait(false)
wann immer dies möglich ist. In Ihrem Fall würde dies ändernAsyncAwait_GetSomeDataAsync
seinvar result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Task
s; es ist denasync
ganzen Weg nach unten. Mit anderen Worten, verwenden Sieawait
anstelle vonGetResult
(Task.Result
undTask.Wait
sollten auch durch ersetzt werdenawait
).Auf diese Weise erhalten Sie beide Vorteile: Die Fortsetzung (der Rest der
AsyncAwait_GetSomeDataAsync
Methode) wird in einem grundlegenden Thread-Pool-Thread ausgeführt, der nicht in den ASP.NET-Anforderungskontext eingegeben werden muss. und der Controller selbst istasync
(der einen Anforderungsthread nicht blockiert).Mehr Informationen:
async
/await
Intro-Beitrag , der eine kurze Beschreibung derTask
Verwendung durch die Kellner enthältSynchronizationContext
.SynchronizationContext
das Kontextanforderung beschränkt auf nur einen Thread zu einem Zeitpunkt.Update 2012-07-13: Diese Antwort wurde in einen Blog-Beitrag aufgenommen .
quelle
SynchroniztaionContext
, die erklärt, dass für eine Anforderung nur ein Thread im Kontext vorhanden sein kann? Wenn nicht, denke ich, sollte es geben.SynchronizationContext
bietet einige wichtige Funktionen: Es fließt der Anforderungskontext. Dies umfasst alle Arten von Dingen, von der Authentifizierung über Cookies bis hin zur Kultur. In ASP.NET synchronisieren Sie also nicht wieder mit der Benutzeroberfläche, sondern wieder mit dem Anforderungskontext. Dies kann sich in Kürze ändern: Das NeueApiController
hat einenHttpRequestMessage
Kontext als Eigenschaft - daher muss der Kontext möglicherweise nicht durchlaufen werdenSynchronizationContext
-, aber ich weiß es noch nicht.Bearbeiten: Versuchen Sie im Allgemeinen, das Folgende zu vermeiden, außer als letzten Versuch, Deadlocks zu vermeiden. Lesen Sie den ersten Kommentar von Stephen Cleary.
Schnelle Lösung von hier . Anstatt zu schreiben:
Versuchen:
Oder wenn Sie ein Ergebnis benötigen:
Aus der Quelle (bearbeitet, um dem obigen Beispiel zu entsprechen):
Für mich scheint dies eine brauchbare Option zu sein, da ich nicht die Möglichkeit habe, sie vollständig asynchron zu machen (was ich bevorzugen würde).
Aus der Quelle:
quelle
async
Code in ASP.NET und kann tatsächlich Probleme bei der Skalierung verursachen. Übrigens,ConfigureAwait
"bricht in keinem Szenario das richtige asynchrone Verhalten"; Es ist genau das, was Sie im Bibliothekscode verwenden sollten.Avoid Exposing Synchronous Wrappers for Asynchronous Implementations
. Der gesamte Rest des Beitrags erklärt einige verschiedene Möglichkeiten, dies zu tun, wenn Sie dies unbedingt benötigen .Da Sie verwenden
.Result
oder.Wait
oderawait
dies wird das verursacht ein Ende Sackgasse in Ihrem Code.Sie verwenden können ,
ConfigureAwait(false)
inasync
für Methoden verhindern Deadlockso was:
quelle
Diese beiden Schulen schließen nicht wirklich aus.
Hier ist das Szenario, in dem Sie einfach verwenden müssen
oder so ähnlich
Ich habe eine MVC-Aktion, die sich unter dem Datenbanktransaktionsattribut befindet. Die Idee war (wahrscheinlich), alles, was in der Aktion getan wurde, zurückzusetzen, wenn etwas schief geht. Dies ermöglicht keine Kontextumschaltung, da sonst das Zurücksetzen oder Festschreiben von Transaktionen selbst fehlschlägt.
Die Bibliothek, die ich benötige, ist asynchron, da erwartet wird, dass sie asynchron ausgeführt wird.
Die einzige Option. Führen Sie es als normalen Synchronisierungsaufruf aus.
Ich sage nur jedem sein eigenes.
quelle
Ich werde dies hier eher der Vollständigkeit halber als der direkten Relevanz für das OP einfügen. Ich verbrachte fast einen Tag damit, eine
HttpClient
Anfrage zu debuggen , und fragte mich, warum ich nie eine Antwort zurückbekam.Schließlich stellte ich fest, dass ich
await
denasync
Anruf weiter unten im Anrufstapel vergessen hatte .Fühlt sich ungefähr so gut an, als würde ein Semikolon fehlen.
quelle
Ich suche hier:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
Und hier:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx
Und sehen:
await
Brauchen Sie wirklich eine Antwort auf diese Frage, wenn man bedenkt, dass die Version funktioniert und die "richtige" Vorgehensweise ist?Meine Stimme lautet: Missbrauch der API .
quelle
Test5Controller.Get()
den Kellner umgestalten , um Folgendes zu eliminieren:var task = AsyncAwait_GetSomeDataAsync(); return task.Result;
Das gleiche Verhalten kann beobachtet werden.