Condividi tramite


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 Mainesegua 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.

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++.

API interessate