IncrementingPollingCounter 初始回调是异步回调

IncrementingPollingCounter 使用回调来检索指标的当前值,并通过 EventSource 事件进行报告。 过去,回调的第一次调用可能已在启用 EventSource 的任何线程上同步发生;将来的调用发生在专用计时器线程上。 从 .NET 9 开始,第一个回调始终在计时器线程上异步发生。 这可能会导致计数器启用后发生的计数器更改无法被观察到,因为第一个回调稍后便会发生。

此更改最有可能影响使用 EventListener 验证 IncrementingPollingCounter 的测试。 如果测试启用计数器,然后立即修改计数器正在轮询的状态,那么在首次调用回调之前,该修改可能会立即发生(并被忽视)。

旧行为

以前,启用 IncrementingPollingCounter 时,回调的第一次调用可能在执行启用操作的线程上同步发生。

此示例应用在调用 EnableEvents() 中的 Main 线程上调用代理 () => SomeInterestingValue。 该回调将观察到 log.SomeInterestingValue 为 0。 以后从专用计时器线程进行的调用将观察到 log.SomeInterestingValue 更改为 1,并将发送 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"]}");
        }
    }
}

新行为

使用与上一行为部分相同的代码片段,回调的第一次调用在计时器线程上异步发生。 在Main线程运行log.SomeInterestingValue++之前,可能会或不会发生这种情况,具体取决于 OS 如何计划多个线程。

根据该时间,应用会输出“Increment=0”或“Increment=1”。

引入的版本

.NET 9 RC 1

中断性变更的类型

此更改为行为更改

更改原因

进行了更改,以解决在 EventListener 锁定时可能发生的回调函数的潜在死锁。

使用 IncrementingPollingCounters 在外部监视工具中可视化指标的方案不需要执行任何操作。 这些方案应继续正常工作。

对于通过 EventListener 进行进程内测试或其他计数器数据的消耗情况,请检查代码是否希望观察在调用 EnableEvents() 的同一线程上对计数器值所做的特定修改。 如果这样做,我们建议等待观察 EventListener 中的至少一个计数器事件,然后修改计数器值。 例如,为了确保示例代码片段打印“Increment=1”,可以向 EventListener 添加 ManualResetEvent,并在收到第一个计数器事件时发出信号,然后在调用 log.SomeInterestingValue++ 之前等待一会儿。

受影响的 API