Freigeben über


Anfänglicher IncrementingPollingCounter-Rückruf ist asynchron

IncrementingPollingCounter verwendet einen Rückruf, um aktuelle Werte einer Metrik abzurufen und über EventSource-Ereignisse zu melden. In der Vergangenheit ist der erste Aufruf des Rückrufs möglicherweise synchron auf einem Thread aufgetreten, der die EventSource aktiviert hat; zukünftige Aufrufe sind in einem dedizierten Timerthread aufgetreten. Ab .NET 9 tritt der erste Rückruf immer asynchron im Timerthread auf. Dies kann zu Leistungsindikatorenänderungen führen, die unmittelbar nach der Aktivierung des Zählers nicht überwacht wurden, da der erste Rückruf später erfolgt ist.

Diese Änderung wirkt sich wahrscheinlich auf Tests aus, die EventListener zum Überprüfen eines IncrementingPollingCounter verwenden. Wenn Tests den Zähler aktivieren und dann sofort den Zustand ändern, der vom Zähler abgefragt wird, kann diese Änderung jetzt vor dem ersten Aufruf des Rückrufs auftreten (und unbemerkt bleiben).

Vorheriges Verhalten

Früher, als ein IncrementingPollingCounter aktiviert wurde, ist der erste Aufruf des Rückrufs möglicherweise synchron auf dem Thread aufgetreten, der den Aktivierungsvorgang ausgeführt hat.

Diese Beispiel-App ruft den Delegat () => SomeInterestingValue im Main-Thread innerhalb des Aufrufs von EnableEvents() auf. Dieser Rückruf wird beobachten , ob log.SomeInterestingValue 0 ist. Ein späterer Aufruf eines dedizierten Timerthreads wird beobachten, ob log.SomeInterestingValue auf 1 geändert wird, und ein Ereignis wird mit Increment value = 1 gesendet.

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"]}");
        }
    }
}

Neues Verhalten

Mit demselben Codeausschnitt wie im Abschnitt Vorheriges Verhalten erfolgt der erste Aufruf des Rückrufs asynchron im Timerthread. Es kann vorkommen oder nicht, bevor der Main-Thread log.SomeInterestingValue++ ausführt, je nachdem, wie das Betriebssystem mehrere Threads plant.

Je nach Timing gibt die App entweder „Increment=0“ oder „Increment=1“ aus.

Eingeführt in Version

.NET 9 RC 1

Typ des Breaking Changes

Diese Änderung ist eine Verhaltensänderung.

Grund für die Änderung

Die Änderung wurde vorgenommen, um einen potenziellen Deadlock aufzulösen, der auftreten kann, wenn die Rückruffunktionen ausgeführt werden, während die EventListener-Sperre aktiv ist.

Für Szenarien, die IncrementingPollingCounters verwenden, um Metriken in externen Überwachungstools zu visualisieren, ist keine Aktion erforderlich. Diese Szenarien sollten weiterhin normal funktionieren.

Überprüfen Sie bei Szenarien, die In-Process-Tests oder andere Verwendung von Leistungsindikatorendaten über EventListener durchführen, ob Ihr Code erwartet, dass eine bestimmte Änderung des Leistungsindikatorwerts im selben Thread beobachtet wird, der EnableEvents() aufgerufen hat. Wenn dies der Fall ist, wird empfohlen, mindestens ein Zählerereignis vom EventListener zu beobachten, und dann den Zählerwert zu ändern. Um beispielsweise sicherzustellen, dass der Beispielcodeausschnitt „Increment=1“ ausgibt, können Sie ein ManualResetEvent dem EventListener hinzufügen, es signalisieren, wenn das erste Zählerereignis empfangen wird, und vor dem Aufrufen von log.SomeInterestingValue++.

Betroffene APIs