What's Changed
Breaking Changes
Start()on a disposed timer now throws anObjectDisposedExceptionsynchronously. Previously this was undefined behavior and could crash the whole process.- The timer constructors no longer throw
InvalidOperationException: kernel resources are created per run insideStart(), which now documents that exception instead. A failing resource creation leaves the timer stopped instead of half-constructed. - The default
SleepThreshold/YieldThresholdof theHighResQuickTickTimerare 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 forSleepThresholdwas
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 whenStop()raced anElapsedhandler that itself callsStop()/Dispose(), theStart()-after-Dispose()process crash, and missing disposed guards. EveryStart()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
HighResQuickTickTimerby @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_nanosleepon the monotonic clock (overshoot of only tens of microseconds) instead ofThread.Sleep(1), drastically reducing CPU usage at equal or better accuracy and enabling the new sub-millisecond default thresholds
- Fixed
Stopwatchticks andTimeSpanticks being mixed up in internal conversions; all conversions are now explicit by @Uight in #59 QuickTickTiming.Delayno 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
TypeInitializationExceptionon Windows versions older than 10 (1803) — the realPlatformNotSupportedExceptionsurfaces at the point of use - The
Elapsedevent is now thread-safe against concurrent subscribe/unsubscribe - Long intervals (hours+) no longer lose millisecond precision due to
floatstorage - 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
- Timers no longer throw a confusing
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