.net Core AsyncLocal verliert seinen Wert
Ich verwende ein ähnliches Muster wie HttpContextAccessor
Die vereinfachte Version ist wie folgt, Console.WriteLine(SimpleStringHolder.StringValue)soll nicht null sein.
public class SimpleStringHolder
{
private static readonly AsyncLocal<ValueHolder> CurrentHolder = new AsyncLocal<ValueHolder>();
public static string StringValue
{
get => CurrentHolder.Value?.StringValue;
set
{
var holder = CurrentHolder.Value;
if (holder != null)
{
holder.StringValue = null;
}
if (value != null)
{
CurrentHolder.Value = new ValueHolder() { StringValue = value };
}
}
}
private class ValueHolder
{
public string StringValue;
}
}
class Program
{
private static readonly AsyncLocal<string> currentValue = new AsyncLocal<string>();
public static void Main(string[] args)
{
var task = Task.Run(async () => await OutterAsync());
task.Wait();
}
public static async Task OutterAsync()
{
SimpleStringHolder.StringValue = "1";
await InnerAsync();
Console.WriteLine(SimpleStringHolder.StringValue); //##### the value is gone ######
}
public static async Task InnerAsync()
{
var lastValue = SimpleStringHolder.StringValue;
await Task.Delay(1).ConfigureAwait(false);
SimpleStringHolder.StringValue = lastValue; // comment this line will make it work
Console.WriteLine(SimpleStringHolder.StringValue); //the value is still here
}
}
Ruft im obigen Code OutterAsynceine asynchrone Methode auf InnerAsync, in InnerAsyncder StringValuegesetzt ist, wodurch AsyncLocal seinen Kontext verliert, in dem OutterAsync Console.WriteLine(SimpleStringHolder.StringValue);null ist.
Ich denke, die Magie liegt im Eigenschaftssatz von SimpleStringHolder. Wenn Sie den folgenden Code entfernen, werden die Dinge richtig.
if (holder != null)
{
holder.StringValue = null;
}
Der obige Code funktioniert wie erwartet.
Bitte helfen Sie mir zu verstehen, was Zauberei ist?
Antworten
AsyncLocal<T>existiert, um einen Mechanismus zum Beibehalten von Werten in einem asynchronen Ausführungskontext bereitzustellen. Der Schlüssel dazu sind zwei Faktoren, die in Ihrem Beispiel eine Rolle spielen:
- Mit
awaitkann eine Methode zum Aufrufer zurückkehren, wodurch sich der Kontext ändern kann.ThreadLocal<T>Wenn beim älteren Typ die Ausführung die Kontrolle an die Methode zurückgibt, kann sie sich in einem anderen Thread befinden, obwohlasyncder Kontext aus der Sicht derselbe ist. Mit using wirdAsyncLocal<T>sichergestellt, dass der Status des Kontexts wiederhergestellt wird, wenn dasawaitSteuerelement nach Abschluss des erwarteten Objekts an die Methode zurückgegeben wird. - Bis etwas passiert, bei dem sich der Kontext ändern müsste, ist der aktuelle Status eines
AsyncLocal<T>Objekts der vorherige. Das heißt, eine Methode erbt im Wesentlichen den Zustand, in dem sich das Objekt befand, als es aufgerufen wurde. Wenn Sie es mit einfachen Werten zu tun haben, lauern keine Überraschungen, aber bei einem Referenztyp wie IhremValueHolderTyp ist das einzige, was SieAsyncLocal<T>verfolgen, die Referenz auf dieses Objekt. Es gibt immer noch nur eine Kopie des Objekts und Änderungen am Status einer bestimmten Objektarbeit, wie sie es immer mit oder ohne herumschwebende asynchrone Kontexte tun (dh sie werden durch einen Verweis auf dieses Objekt gesehen).
In dem von Ihnen angegebenen Codebeispiel:
OutterAsync()Setzt dieStringValueEigenschaft auf"1", wodurch ein neuesValueHolderObjekt erstellt wird, und dieStringValueEigenschaft dieses Objekts auf"1".OutterAsync()AnrufeInnerAsync(). Diese Methode ruft dann diestringReferenz vom Inhaber ab (indirekt… dh durch Durchlaufen derSimpleStringHolder.StringValueEigenschaft). Da zu diesem Zeitpunkt weder Änderungen am Wert noch am Kontext vorgenommen wurden,ValueHolderwird in diesem Fall dasselbe Objekt verwendet, sodass Sie"1"zurückkehren.InnerAsync()wartet auf eine asynchrone Aufgabe, wodurch ein neuer Ausführungskontext erstellt wird, um Änderungen amAsyncValue<T>Objekt in diesem Kontext zu isolieren . Ab diesem Zeitpunkt werden Änderungen am Objekt vom Code nicht mehr in einem anderen Kontext angezeigt. Zum Beispiel der Code, der in derOutterAsync()Methode ausgeführt wird.- Nachdem die asynchrone Aufgabe abgeschlossen
InnerAsync()ist, legt diese Methode einen neuen Wert für dieSimpleStringHolder.StringValueEigenschaft fest. Da der frühere Kontext geerbt wurde, setzt der Setterholder.StringValuebeimnullFestlegen auf die Eigenschaft des Objekts, in dem er erstellt wurdeOutterAsync(). Aber… weil sich der Code in einem neuen Kontext befindet,CurrentHolder.Valuewird diese Änderung in diesem Kontext isoliert , wenn der Setter der Eigenschaft dann einen neuen Wert zuweist . - Wenn die
InnerAsync()Methode endgültig abgeschlossen ist, ist die Aufgabe abgeschlossen, auf die dieOutterAsync()Methodeawaitgewartet hat. Dadurch wird derAsyncValue<T>Status derOutterAsync()Methode im Kontext der Methode wiederhergestellt , der sich von dem Kontext unterscheidet, in demInnerAsync()derSimpleStringHolder.StringValueWert aktualisiert wurde . Insbesondere ist dieser wiederhergestellte Status ein Verweis auf dasValueHolderObjekt, das ursprünglich festgelegt wurde,SimpleStringHolderals dieholder.StringValueEigenschaft auf null gesetzt wurde. - Wenn Sie sich
OutterAsync()dann den Eigenschaftswert ansehen, wird er auf null gesetzt. Weil es auf null gesetzt wurde.
In Ihrem eigenen Experimentieren, können Sie entweder die Nullzuordnung löschen ganz oder einfach weglassen , die Abtretung SimpleStringHolder.StringValuenach der InnerAsync()‚s awaitAussage (weil , wenn Sie nicht die Zuordnung machen, dann die Null Zuordnung nie ausgeführt wird). In beiden Fällen findet die Nullzuweisung nicht statt, sodass der zuvor zugewiesene Wert erhalten bleibt.
Aber wenn Sie die Null Zuordnung vornehmen zu tun, der Anrufer OutterAsync()wird seinen Kontext haben , wieder hergestellt und haben den Objekthalter Referenz dann wiederhergestellt, und das eigene des Inhabers Objekt stringBezug hatte bereits Satz null, so dass das , was OutterAsync()sieht.
Verwandte
Themen : Wie wirkt sich AsyncLocal auf nicht asynchronen / erwarteten Code aus?
Warum gibt AsyncLocal unterschiedliche Ergebnisse zurück, wenn der Code geringfügig überarbeitet wird?
Tut AsyncLocalauch die Dinge, ThreadLocaldie tun ?