Skip to content

v3.0.0

Latest

Choose a tag to compare

@Uight Uight released this 11 Jul 14:31

What's Changed

Breaking Changes

  • Start() on a disposed timer now throws an ObjectDisposedException synchronously. Previously this was undefined behavior and could crash the whole process.
  • The timer constructors no longer throw InvalidOperationException: kernel resources are created per run inside Start(), which now documents that exception instead. A failing resource creation leaves the timer stopped instead of half-constructed.
  • The default SleepThreshold/YieldThreshold of the HighResQuickTickTimer are now platform dependent (Windows 1.5 / 0.75 ms, Linux 0.5 / 0.3 ms, others e.g. macOS 15 / 0.75 ms) instead of a fixed 1.5 / 0.75 ms. Explicitly set values behave as before; the allowed minimum for SleepThreshold was
    lowered from 1.0 to 0.5 ms.

Library Changes

  • Removed the IOCP machinery: the timer now waits on the high-resolution timer handle directly. The Go-style completion port routing the implementation was originally based on is not needed in .NET by @Uight in #68
  • Fixed the Stop()/Dispose() lifecycle: a deadlock when Stop() raced an Elapsed handler that itself calls Stop()/Dispose(), the Start()-after-Dispose() process crash, and missing disposed guards. Every Start() now owns its kernel resources per run and the worker thread releases them
    itself, so runs can no longer interfere with each other by @Uight in #69
  • Reworked the sleep logic of the HighResQuickTickTimer by @Uight in #70:
    • Each run now caches one kernel timer instead of creating and destroying a handle for every 0.4 ms sleep
    • Long intervals are slept in a single interruptible block until shortly before the deadline — one wakeup instead of hundreds per tick, with unchanged timing behavior near the deadline
    • On Linux the timer now sleeps via clock_nanosleep on the monotonic clock (overshoot of only tens of microseconds) instead of Thread.Sleep(1), drastically reducing CPU usage at equal or better accuracy and enabling the new sub-millisecond default thresholds
  • Fixed Stopwatch ticks and TimeSpan ticks being mixed up in internal conversions; all conversions are now explicit by @Uight in #59
  • QuickTickTiming.Delay no longer runs task continuations synchronously on the timer/cancellation callback thread by @Uight in #61
  • Several smaller fixes by @Uight in #63:
    • Timers no longer throw a confusing TypeInitializationException on Windows versions older than 10 (1803) — the real PlatformNotSupportedException surfaces at the point of use
    • The Elapsed event is now thread-safe against concurrent subscribe/unsubscribe
    • Long intervals (hours+) no longer lose millisecond precision due to float storage
    • Symbol packages (.snupkg) are now actually published and SourceLink was added for step-through debugging
    • Fixed the NuGet package description to mention all supported target frameworks
    • Timer worker threads are now named "QuickTick Timer" for easier debugging

Documentation & Reports

  • The README now documents the exceptions of every public method as well as the new sleep behavior and per-platform defaults
  • Updated performance reports to 3.0 for Windows Server 2025 and Ubuntu 24.04.4 (both .NET 10.0)

Dependency & Tooling Updates

Note: These dependency updates only affect tooling/testing projects and are not part of the released QuickTick library package.

  • Bump SkiaSharp by @dependabot[bot] in #56
  • Bump actions/checkout from 6 to 7 by @dependabot[bot] in #53
  • Bump Microsoft.NET.Test.Sdk from 18.5.1 to 18.7.0 by @dependabot[bot] in #54
  • Bump NUnit from 4.6.0 to 4.6.1
  • Bump QuestPDF from 2026.5.0 to 2026.7.0

Full Changelog: v2.3.0...v3.0.0