.net core AsyncLocal perd sa valeur
J'utilise un modèle similaire à HttpContextAccessor
La version simplifiée est la suivante, Console.WriteLine(SimpleStringHolder.StringValue)n'est pas censée être nulle.
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
}
}
Dans le code ci - dessus, OutterAsyncappelle une méthode asynchrone InnerAsync, dans InnerAsyncl' StringValueest réglée, ce qui rend AsyncLocal perd son contexte OutterAsync Console.WriteLine(SimpleStringHolder.StringValue);est nulle.
Je pense que la magie est dans l'ensemble de propriétés de SimpleStringHolder, la suppression du code suivant arrangera les choses.
if (holder != null)
{
holder.StringValue = null;
}
Le code ci-dessus fonctionne comme prévu.
S'il vous plaît, aidez-moi à comprendre de quoi s'agit-il?
Réponses
AsyncLocal<T>existe pour fournir un mécanisme permettant de conserver les valeurs dans un contexte d'exécution asynchrone. La clé de ceci est deux facteurs impliqués dans votre exemple:
- An
awaitpermet à une méthode de revenir à l'appelant, ce qui pourrait changer le contexte. Avec l'ancienThreadLocal<T>type, lorsque l'exécution renvoie le contrôle à la méthode, elle peut être dans un thread différent, même si duasyncpoint de vue le contexte est le même. L'utilisationAsyncLocal<T>garantit que l'état du contexte est restauré lorsque leawaitretourne le contrôle à la méthode une fois que l'objet attendu est terminé. - Jusqu'à ce que quelque chose se produise qui exigerait que le contexte change, l'état actuel d'un
AsyncLocal<T>objet est ce qu'il était auparavant. C'est-à-dire qu'une méthode hérite essentiellement de l'état dans lequel se trouvait l'objet lorsqu'il a été appelé. Si vous avez affaire à des valeurs simples, aucune surprise ne se cache, mais dans le cas d'un type de référence comme votreValueHoldertype, la seule chose quiAsyncLocal<T>garde une trace est la référence à cet objet. Il n'y a toujours qu'une seule copie de l'objet, et les changements d'état de tout objet donné fonctionnent comme ils le font toujours avec ou sans contextes asynchrones flottant (c'est-à-dire qu'ils sont vus par toute référence à cet objet).
Donc, dans l'exemple de code que vous avez fourni:
OutterAsync()définit laStringValuepropriété sur"1", ce qui entraîne la création d'un nouvelValueHolderobjet et laStringValuepropriété de cet objet définie sur"1".OutterAsync()appelsInnerAsync(). Cette méthode récupère ensuite lastringréférence du détenteur (indirectement… c'est-à-dire en passant par laSimpleStringHolder.StringValuepropriété). Comme aucune modification de la valeur ni du contexte n'a été effectuée à ce stade, le mêmeValueHolderobjet est utilisé dans ce cas, vous"1"revenez donc.InnerAsync()attend une tâche asynchrone, ce qui entraîne la création d'un nouveau contexte d'exécution dans le but d'isoler les modifications apportées à l'AsyncValue<T>objet dans ce contexte. À partir de ce moment, les modifications apportées à l'objet ne sont pas vues par le code dans un contexte différent. Par exemple, le code s'exécutant dans laOutterAsync()méthode.- Une fois la tâche asynchrone terminée
InnerAsync(), cette méthode définit une nouvelle valeur pour laSimpleStringHolder.StringValuepropriété. Étant donné que le contexte antérieur a été hérité, lorsque le paramètre définitholder.StringValuesurnull, il définit la propriété de l'objet qui a été créé dansOutterAsync(). Mais… parce que le code est dans un nouveau contexte, lorsque le setter attribue ensuite une nouvelle valeur à laCurrentHolder.Valuepropriété, ce changement est isolé dans ce contexte. - Lorsque la
InnerAsync()méthode est enfin terminée, cela termine la tâche que laOutterAsync()méthodeawaitattendait. Cela oblige leAsyncValue<T>à restaurer son état dans leOutterAsync()contexte de la méthode, qui est différent du contexte dansInnerAsync()lequel il a mis à jour laSimpleStringHolder.StringValuevaleur. Et plus précisément, cet état restauré est une référence à l'ValueHolderobjet qui a été initialement défini dans leSimpleStringHolderlorsque laholder.StringValuepropriété a été définie sur null. - Ainsi, lorsqu'il
OutterAsync()examine ensuite la valeur de la propriété, il la trouve définie sur null. Parce qu'il a été défini sur null.
Dans votre propre test, vous pouvez soit supprimer complètement l'affectation nulle, soit simplement omettre l'affectation SimpleStringHolder.StringValueaprès l' instruction InnerAsync()s await(car si vous n'effectuez pas l'affectation, l'affectation nulle n'est jamais exécutée). Dans tous les cas, l'affectation nulle ne se produit pas et la valeur précédemment attribuée reste donc.
Mais si vous effectuez une affectation nulle, l'appelant OutterAsync()va avoir son contexte restauré, et la référence d'objet détenteur ensuite restaurée, et la propre stringréférence de cet objet détenteur a déjà été définie sur null, c'est donc ce que OutterAsync()voit.
Lecture connexe:
Quel est l'effet d'AsyncLocal dans le code non async / d'attente?
Pourquoi AsyncLocal renvoie-t-il des résultats différents lorsque le code est légèrement remanié?
Fait-il AsyncLocalaussi les choses qui le ThreadLocalfont?