IncrementingPollingCounter początkowe wywołanie zwrotne jest asynchroniczne
IncrementingPollingCounter używa wywołania zwrotnego, aby pobrać bieżące wartości metryki i zgłosić je za pośrednictwem EventSource zdarzeń. W przeszłości pierwsze wywołanie wywołania zwrotnego mogło mieć miejsce synchronicznie na dowolnym wątku, który włączał EventSource
; przyszłe wywołania miały miejsce w dedykowanym wątku czasomierza. Począwszy od platformy .NET 9, pierwsze wywołanie zwrotne zawsze występuje asynchronicznie w wątku czasomierza. Może to spowodować zmiany licznika, które wystąpiły tuż po włączeniu licznika będzie nieobserwowane, ponieważ pierwsze wywołanie zwrotne nastąpi później.
Ta zmiana najprawdopodobniej wpłynie na testy, które używają EventListener do weryfikowania elementu IncrementingPollingCounter
. Jeśli testy włączą licznik, a następnie natychmiast zmodyfikuj stan sondowany przez licznik, ta modyfikacja może teraz wystąpić przed pierwszym wywołaniem wywołania zwrotnego (i przejdź do niezauważonego).
Poprzednie zachowanie
Wcześniej, gdy włączono funkcję IncrementingPollingCounter
, pierwsze wywołanie wywołania zwrotnego mogło wystąpić synchronicznie w wątku, który wykonał operację włączania.
Ta przykładowa aplikacja wywołuje delegata () => SomeInterestingValue
w wątku Main
w wywołaniu metody EnableEvents()
. To wywołanie zwrotne będzie obserwowane log.SomeInterestingValue
jest 0. Późniejsze wywołanie z dedykowanego wątku czasomierza będzie obserwować log.SomeInterestingValue
zmianę na 1, a zdarzenie zostanie wysłane za pomocą polecenia 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"]}");
}
}
}
Nowe zachowanie
Używając tego samego fragmentu kodu co w sekcji Poprzednie zachowanie , pierwsze wywołanie wywołania zwrotnego odbywa się asynchronicznie w wątku czasomierza. Może to wystąpić lub nie występuje przed uruchomieniem log.SomeInterestingValue++
wątku Main
w zależności od tego, jak system operacyjny planuje wiele wątków.
W zależności od tego czasu aplikacja generuje dane wyjściowe "Increment=0" lub "Increment=1".
Wprowadzona wersja
.NET 9 RC 1
Typ zmiany powodującej niezgodność
Ta zmiana jest zmianą behawioralną.
Przyczyna wprowadzenia zmiany
Wprowadzono zmianę w celu rozwiązania potencjalnego zakleszczenia, które mogą wystąpić podczas uruchamiania EventListener
funkcji wywołania zwrotnego podczas blokowania.
Zalecana akcja
W scenariuszach używanych IncrementingPollingCounters
do wizualizacji metryk w narzędziach do monitorowania zewnętrznego nie jest wymagana żadna akcja. Te scenariusze powinny nadal działać normalnie.
W przypadku scenariuszy, które przeprowadzają testowanie w procesie lub inne użycie danych licznika za pośrednictwem metody EventListener
, sprawdź, czy kod oczekuje obserwowania określonej modyfikacji wartości licznika wykonanej w tym samym wątku, który nazwał EnableEvents()
. Jeśli tak, zalecamy oczekiwanie na obserwowanie co najmniej jednego zdarzenia licznika EventListener
z klasy , a następnie zmodyfikowanie wartości licznika. Aby na przykład upewnić się, że przykładowy fragment kodu drukuje wartość "Increment=1", można dodać element ManualResetEvent
do elementu , zasygnalizować go po odebraniu EventListener
pierwszego zdarzenia licznika i zaczekać na nie przed wywołaniem metody log.SomeInterestingValue++
.