Compartilhar via


O retorno de chamada inicial IncrementingPollingCounter é assíncrono

IncrementingPollingCounter usa um retorno de chamada para recuperar os valores atuais de uma métrica e relatá-los por meio de eventos EventSource. No passado, a primeira invocação do retorno de chamada pode 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 temporizador. Isso pode fazer com que as alterações de contador que ocorreram logo após o contador ter sido habilitado não sejam observadas porque o primeiro retorno de chamada ocorre mais tarde.

É mais provável que essa alteração afete os testes que usam EventListener para validar um IncrementingPollingCounter. 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 era habilitado, a primeira invocação do retorno de chamada pode 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 Main thread dentro da chamada para EnableEvents(). Esse retorno de chamada observará log.SomeInterestingValue é 0. 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 snippet de código que a 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 Main execução do thread log.SomeInterestingValue++ dependendo de como o sistema operacional agenda vários threads.

Dependendo desse tempo, o aplicativo gera "Incremento = 0" ou "Incremento = 1".

Versão introduzida

.NET 9 RC 1

Tipo de alteração interruptiva

Esta é uma alteração comportamental.

Motivo da alteração

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

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

Para cenários que fazem testes em processo ou outro consumo de dados de contador via EventListener, verifique se o código espera observar uma modificação específica no valor do contador feita no mesmo thread que chamou EnableEvents(). Em caso afirmativo, recomendamos aguardar para observar pelo menos um evento de contador do EventListener e, em seguida, modificar 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