La devolución de llamada inicial de IncrementingPollingCounter es asincrónica
IncrementingPollingCounter usa una devolución de llamada para recuperar los valores actuales de una métrica y la registra a través de eventos EventSource. Anteriormente, la primera invocación de la devolución de llamada podría producirse sincrónicamente en cualquier subproceso que habilitara EventSource
; las siguientes invocaciones se producían en un subproceso del temporizador específico. A partir de .NET 9, la primera devolución de llamada siempre se produce de forma asincrónica en el subproceso del temporizador. Esto podría dar lugar a cambios en el contador que se producían justo después de que el contador se habilitara si estar visible porque la primera devolución de llamada se generaba después.
Este cambio es más probable que afecte a las pruebas de choque que usan EventListener para validar un IncrementingPollingCounter
. Si las pruebas habilitan el contador y luego modifican de inmediato el estado que inspecciona el contador, es posible que la modificación se produzca antes de la primera vez que se invoque la devolución de llamada (y pase desapercibida).
Qué pasaba antes
Anteriormente, cuando se habilitaba una IncrementingPollingCounter
, la primera invocación de la devolución de llamada podía producirse sincrónicamente en el subproceso que realizó la operación de habilitación.
Esta aplicación de ejemplo llama al delegado () => SomeInterestingValue
en el subproceso Main
en la llamada a EnableEvents()
. Esa devolución de llamada detectará que log.SomeInterestingValue
es 0. Una llamada posterior a través de un subproceso del temporizador específico detectará que log.SomeInterestingValue
ha pasado a 1 y se enviará un evento 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"]}");
}
}
}
Nuevo funcionamiento
Con el mismo fragmento de código que la sección Funcionamiento anterior, la primera invocación de la devolución de llamada se produce de forma asincrónica en el subproceso del temporizador. Es posible que se produzca o no antes de que el subproceso Main
ejecute log.SomeInterestingValue++
, en función de cómo el sistema operativo planifica los diferentes subprocesos.
Dependiendo de este tiempo, la aplicación genera "Increment=0" o "Increment=1".
Versión introducida
.NET 9 RC 1
Tipo de cambio importante
Este es un cambio de funcionamiento.
Motivo del cambio
El cambio se ha realizado para resolver un posible interbloqueo que puede producirse al ejecutar funciones de devolución de llamada mientras se mantiene el bloqueo de EventListener
.
Acción recomendada
No hace falta hacer nada en casos donde se usen IncrementingPollingCounters
para ver métricas en herramientas de seguimiento externas. En estos casos, todo debería seguir funcionando con normalidad.
En contextos donde hay pruebas dentro de los procesos o se realiza otro consumo de datos del contador a través de EventListener
, compruebe si el código tiene previsto detectar una modificación específica del valor del contador realizado en el mismo subproceso que llamó a EnableEvents()
. Si es así, le recomendamos que se espere a detectar al menos un evento de contador del EventListener
, para luego modificar el valor del contador. Por ejemplo, para garantizar que el fragmento de código de ejemplo imprime "Increment=1", podría agregar un ManualResetEvent
al EventListener
, señalarlo cuando se recibe el primer evento del contador y esperarlo antes de llamar a log.SomeInterestingValue++
.