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.
Empfohlene Maßnahme
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++
.