using System.Diagnostics; namespace ZymonicServices; /// Call from a background timer. At most one callback may be waiting on the dispatcher. public sealed class ZymonicDispatcherProbe { private readonly Action _post; private int _pending; private int _generation; public ZymonicDispatcherProbe(Action post) => _post = post; // Keep an outstanding callback outstanding, but discard its timing after suspension or disabling. public void Invalidate() => Interlocked.Increment(ref _generation); public void Poll(ZymonicPerformanceRecorder recorder) { if (Interlocked.CompareExchange(ref _pending, 1, 0) == 0) { int generation = Volatile.Read(ref _generation); long started = Stopwatch.GetTimestamp(); try { _post(() => { try { if (generation == Volatile.Read(ref _generation)) { recorder.RecordElapsed("ui.dispatch", started); } } finally { Volatile.Write(ref _pending, 0); } }); } catch { // A dispatcher can reject a post during shutdown. Diagnostics must not crash the app. Volatile.Write(ref _pending, 0); } } } }