Partager via


IncrementingPollingCounter, le premier rappel est asynchrone

IncrementingPollingCounter utilise un rappel pour récupérer les valeurs actuelles d’une métrique et les rapporte via des événements EventSource. Dans le passé, la première invocation du rappel pouvait se produire de manière synchrone sur n’importe quel thread activant le EventSource; les invocations futures se produisaient sur un thread de minuterie dédié. À partir de .NET 9, le premier rappel se produit toujours de manière asynchrone sur le thread de la minuterie. Cela peut entraîner l’absence d’observation des changements de compteur survenus juste après l’activation du compteur, car le premier rappel survient plus tard.

Ce changement est le plus susceptible d’impacter les tests qui utilisent EventListener pour valider un IncrementingPollingCounter. Si les tests activent le compteur et modifient immédiatement l’état surveillé par le compteur, cette modification peut maintenant survenir avant la première invocation du rappel (et passer inaperçue).

Comportement précédent

Auparavant, lorsqu’un IncrementingPollingCounter était activé, la première invocation du rappel pouvait se produire de manière synchrone sur le thread qui effectuait l’opération d’activation.

Cet exemple d’application appelle le délégué () => SomeInterestingValue sur le thread Main dans l’appel à EnableEvents(). Ce rappel observera que log.SomeInterestingValue est 0. Un appel ultérieur depuis un thread de minuterie dédié observera que log.SomeInterestingValue a changé à 1, et un événement sera envoyé avec Increment value = 1.

using System.Diagnostics.Tracing;

var log = MyEventSource.Log;
using var listener = new Listener();

log.SomeInterestingValue++;

Console.ReadKey();

class MyEventSource : EventSource
{
    public static MyEventSource Log { get; } = new();
    private IncrementingPollingCounter? _counter;
    public int SomeInterestingValue;

    private MyEventSource() : base(nameof(MyEventSource))
    {
        _counter = new IncrementingPollingCounter("counter", this, () => SomeInterestingValue);
    }
}

class Listener : EventListener
{
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        if (eventSource.Name == nameof(MyEventSource))
        {
            EnableEvents(eventSource, EventLevel.Informational, EventKeywords.None,
                new Dictionary<string, string?> { { "EventCounterIntervalSec", "1.0" } });
        }
    }

    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        if (eventData.EventSource.Name == "EventCounters")
        {
            var counters = (IDictionary<string, object>)eventData.Payload![0]!;
            Console.WriteLine($"Increment: {counters["Increment"]}");
        }
    }
}

Nouveau comportement

En utilisant le même extrait de code que dans la section Comportement précédent, la première invocation du rappel se produit de manière asynchrone sur le thread de la minuterie. Cela peut ou non se produire avant que le thread Main n’exécute log.SomeInterestingValue++, en fonction de la manière dont le système d’exploitation planifie plusieurs threads.

Selon ce timing, l’application affichera soit « Increment=0 » soit « Increment=1 ».

Version introduite

.NET 9 RC 1

Type de changement cassant

Ce changement est un changement de comportement.

Raison du changement

Le changement a été fait pour résoudre un blocage potentiel qui peut survenir lors de l’exécution des fonctions de rappel pendant que le verrou EventListener est maintenu.

Aucune action n’est requise pour les scénarios qui utilisent IncrementingPollingCounters pour visualiser des métriques dans des outils de surveillance externes. Ces scénarios devraient continuer à fonctionner normalement.

Pour les scénarios qui effectuent des tests en processus ou toute autre consommation de données de compteur via EventListener, vérifiez si votre code s’attend à observer une modification spécifique de la valeur du compteur effectuée sur le même thread qui a appelé EnableEvents(). Si c’est le cas, nous vous recommandons d’attendre d’observer au moins un événement de compteur provenant de EventListener, puis de modifier la valeur du compteur. Par exemple, pour vous assurer que l’extrait de code imprimera « Increment=1 », vous pourriez ajouter un ManualResetEvent au EventListener, le signaler lorsque le premier événement de compteur est reçu, et attendre celui-ci avant d’appeler log.SomeInterestingValue++.

API affectées