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++
之前等待一会儿。