Partilhar via


O retorno de chamada inicial do IncrementingPollingCounter é assíncrono

IncrementingPollingCounter usa um retorno de chamada para recuperar valores atuais de uma métrica e relata-lo por meio EventSource de eventos. No passado, a primeira invocação do retorno de chamada poderia ter ocorrido de forma síncrona em qualquer thread que estivesse habilitando o EventSource; invocações futuras ocorreram em um thread de temporizador dedicado. A partir do .NET 9, o primeiro retorno de chamada sempre ocorre de forma assíncrona no thread do timer. Isso pode fazer com que as alterações do contador que ocorreram logo após a ativação do contador não sejam observadas porque o primeiro retorno de chamada acontece mais tarde.

É mais provável que essa alteração afete os testes que usam EventListener para validar um IncrementingPollingCounterarquivo . Se os testes habilitarem o contador e, em seguida, modificarem imediatamente o estado que está sendo sondado pelo contador, essa modificação poderá ocorrer antes da primeira vez que o retorno de chamada for invocado (e passar despercebido).

Comportamento anterior

Anteriormente, quando um IncrementingPollingCounter estava habilitado, a primeira invocação do retorno de chamada poderia ter ocorrido de forma síncrona no thread que executou a operação de habilitação.

Este aplicativo de exemplo chama o delegado () => SomeInterestingValue no thread dentro da chamada para EnableEvents().Main Esse retorno de chamada será 0 log.SomeInterestingValue . Uma chamada posterior de um thread de temporizador dedicado observará log.SomeInterestingValue alterado para 1, e um evento será enviado com 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"]}");
        }
    }
}

Novo comportamento

Usando o mesmo trecho de código da seção Comportamento anterior, a primeira invocação do retorno de chamada ocorre de forma assíncrona no thread do temporizador. Isso pode ou não ocorrer antes da execução log.SomeInterestingValue++ do Main thread, dependendo de como o sistema operacional agenda vários threads.

Dependendo desse tempo, o aplicativo produz "Increment=0" ou "Increment=1".

Versão introduzida

.NET 9 RC 1

Tipo de mudança de rutura

Esta mudança é uma mudança comportamental.

Razão para a alteração

A alteração foi feita para resolver um possível impasse que pode ocorrer executando funções de retorno de chamada enquanto o EventListener bloqueio é mantido.

Nenhuma ação é necessária para cenários que usam IncrementingPollingCounters para visualizar métricas em ferramentas de monitoramento externas. Estes cenários devem continuar a funcionar normalmente.

Para cenários que fazem testes em processo ou outro consumo de dados do contador via EventListener, verifique se seu código espera observar uma modificação específica no valor do contador feita no mesmo thread chamado EnableEvents(). Se isso acontecer, recomendamos esperar para observar pelo menos um evento de contador do EventListener, em seguida, modificando o valor do contador. Por exemplo, para garantir que o trecho de código de exemplo imprima "Increment=1", você pode adicionar um ManualResetEvent ao EventListener, sinalizá-lo quando o primeiro evento de contador for recebido e aguardar por ele antes de chamar log.SomeInterestingValue++.

APIs afetadas