Il callback iniziale IncrementingPollingCounter è asincrono
IncrementingPollingCounter usa un callback per recuperare i valori correnti di una metrica e lo segnala tramite eventi EventSource. In passato, la prima chiamata del callback potrebbe essersi verificata in modo sincrono su qualsiasi thread che abilitasse EventSource
; le chiamate future si sono verificate in un thread timer dedicato. A partire da .NET 9, il primo callback si verifica sempre in modo asincrono nel thread timer. Questo potrebbe comportare modifiche al contatore subito dopo l'abilitazione del contatore e non rilevate perché il primo callback si verifica in un secondo momento.
Questa modifica è molto probabile che influisca su test che usano EventListener per convalidare un oggetto IncrementingPollingCounter
. Se i test abilitano il contatore e modificano immediatamente lo stato di cui viene eseguito il polling dal contatore, tale modifica potrebbe verificarsi prima della prima chiamata al callback (e non viene rilevata).
Comportamento precedente
In precedenza, quando un IncrementingPollingCounter
è stato abilitato, la prima chiamata del callback potrebbe essersi verificata in modo sincrono sul thread che ha eseguito l'operazione di abilitazione.
Questa app di esempio chiama il delegato () => SomeInterestingValue
nel Main
thread all'interno della chiamata a EnableEvents()
. Tale callback osserverà che log.SomeInterestingValue
è 0. Una chiamata successiva da un thread timer dedicato osserverà che log.SomeInterestingValue
è stato modificato in 1 e un evento verrà inviato con 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"]}");
}
}
}
Nuovo comportamento
Usando lo stesso frammento di codice della sezione Comportamento precedente, la prima chiamata del callback viene eseguita in modo asincrono nel thread timer. Potrebbe verificarsi o meno prima che il tread Main
esegua log.SomeInterestingValue++
, a seconda del modo in cui il sistema operativo pianifica più thread.
A seconda di tale intervallo, l'app restituisce "Increment=0" o "Increment=1".
Versione introdotta
.NET 9 RC 1
Tipo di modifica che causa un'interruzione
Questa è una modifica funzionale.
Motivo della modifica
La modifica è stata apportata per risolvere un potenziale deadlock, che può verificarsi eseguendo funzioni di callback mentre il blocco EventListener
viene mantenuto.
Azione consigliata
Non è necessaria alcuna azione per gli scenari che usano IncrementingPollingCounters
per visualizzare le metriche negli strumenti di monitoraggio esterni. Questi scenari devono continuare a funzionare normalmente.
Per gli scenari che eseguono test in-process o altri consumi di dati del contatore tramite EventListener
, verificare se il codice prevede di osservare una modifica specifica del valore del contatore eseguito sullo stesso thread che ha chiamato EnableEvents()
. In caso affermativo, è consigliabile attendere di osservare almeno un evento del contatore da EventListener
, quindi modificare il valore del contatore. Ad esempio, per assicurarsi che il frammento di codice di esempio stampi "Increment=1", è possibile aggiungere un ManualResetEvent
a EventListener
, segnalarlo quando viene ricevuto il primo evento del contatore e attendere prima di chiamare log.SomeInterestingValue++
.