Proxar.HashedWheelTimer 1.0.1

dotnet add package Proxar.HashedWheelTimer --version 1.0.1
                    
NuGet\Install-Package Proxar.HashedWheelTimer -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Proxar.HashedWheelTimer" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Proxar.HashedWheelTimer" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Proxar.HashedWheelTimer" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Proxar.HashedWheelTimer --version 1.0.1
                    
#r "nuget: Proxar.HashedWheelTimer, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Proxar.HashedWheelTimer@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Proxar.HashedWheelTimer&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Proxar.HashedWheelTimer&version=1.0.1
                    
Install as a Cake Tool

High Performance Timer for .NET

HashedWheelTimer implemented in C# inspired by io.netty.util.HashedWheelTimer

Fork Note

This project is based on https://github.com/wangjia184/HashedWheelTimer.

Key Modification: The main modification is replacing the Task.Run execution model with direct invocation of TimerTask.Run for expired timers. This allow control that all timeout callbacks are executed sequentially on a single thread.

What is Hashed Wheel Timer?

It is a timer based on George Varghese and Tony Lauck's paper, Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility.

alternate text is missing from this package README image

The concept on the Timer Wheel is rather simple to understand: in order to keep track of events on given resolution, an array of linked lists (alternatively - sets or even arrays, YMMV) is preallocated. When event is scheduled, it's address is found by dividing deadline time t by resolution and wheel size. The registration is then assigned with rounds (how many times we should go around the wheel in order for the time period to be elapsed).

For each scheduled resolution, a bucket is created. There are wheel size buckets, each one of which is holding Registrations. Timer is going through each bucket from the first until the next one, and decrements rounds for each registration. As soon as registration's rounds is reaching 0, the timeout is triggered. After that it is either rescheduled (with same offset and amount of rounds as initially) or removed from timer.

Hashed Wheel is often called approximated timer, since it acts on the certain resolution, which allows it's optimisations. All the tasks scheduled for the timer period lower than the resolution or "between" resolution steps will be rounded to the "ceiling" (for example, given resolution 10 milliseconds, all the tasks for 5,6,7 etc milliseconds will first fire after 10, and 15, 16, 17 will first trigger after 20).

If you're a visual person, it might be useful for you to check out these slides, which help to understand the concept underlying the Hashed Wheel Timer better.

Why another Timer?

The .NET Framework and .NET Core already provide a set of timers

  • System.Timers.Timer
  • System.Threading.Timer
  • System.Windows.Forms.Timer
  • System.Web.UI.Timer
  • System.Windows.Threading.DispatcherTimer

HashedWheelTimer is different as it is optimized for approximated I/O timeout scheduling. It provides O(1) time complexity and cheap constant factors for the important operations of inserting or removing timers.

Imagine a scenario, there are ten thousands of TCP connections and each of them has its own timer for rate limit or other functionalities. In this case HashedWheelTimer is a better choice.

Installation

The package is available from NuGet

Install-Package HashedWheelTimer

How to Use

First create an instance of HashedWheelTimer class.

Note that, each HashedWheelTimer instance creates a dedicated thread to watch the wheel. Hence, it is strong recommanded to reuse HashedWheelTimer instance as much as possible.

using HWT;

HashedWheelTimer timer = new HashedWheelTimer( tickDuration: TimeSpan.FromSeconds(1)
                , ticksPerWheel: 100000
                , maxPendingTimeouts: 0);

The constructor accepts the parameters below.

  • tickDuration As described with 'approximated', this timer does not execute the scheduled TimerTask on time. HashedWheelTimer, on every tick, will check if there are any TimerTasks behind the schedule and execute them. You can increase or decrease the accuracy of the execution timing by specifying smaller or larger tick duration in the constructor. In most network applications, I/O timeout does not need to be accurate.
  • ticksPerWheel HashedWheelTimer maintains a data structure called 'wheel'. To put simply, a wheel is a hash table of TimerTasks whose hash function is 'dead line of the task'. The default number of ticks per wheel (i.e. the size of the wheel) is 512. You could specify a larger value if you are going to schedule a lot of timeouts.
  • maxPendingTimeouts The maximum number of pending timeouts after which call to NewTimeout() will result in InvalidOperationException being thrown. No maximum pending timeouts limit is assumed if this value is 0 or negative.

Next, create a new class implementing TimerTask interface

class MyTimerTask : TimerTask
{
    // This method is called when the task is expired.
    // It is fired via `Task.Run`, hence you'd better surround the code with try-catch
    public void Run(Timeout timeout)
    {
        try
        {
            // ...
        } catch(Exception){
            // ...
        }
    }
}

Now we can schedual the task in 5 seconds later.

timer.NewTimeout(new MyTimerTask(), TimeSpan.FromSeconds(5));

Note that all the methods are thread-safe. You don't need synchronization on accessing them.

If you are using TPL(Task Parallel Library) asynchronous programming, you may already use await Task.Delay(milliseconds) to continue some work after a while. Alternatively, it can be replaced with following code if approximated delay is acceptable.

await timer.Delay(milliseconds)

A full example

/// <summary>
/// Task fired repeatedly
/// </summary>
class IntervalTimerTask : TimerTask
{
    public void Run(Timeout timeout)
    {
        Console.WriteLine($"IntervalTimerTask is fired at {DateTime.UtcNow.Ticks / 10000000L}");
        timeout.Timer.NewTimeout(this, TimeSpan.FromSeconds(2));
    }
}

/// <summary>
/// Task only be fired for one time
/// </summary>
class OneTimeTask : TimerTask
{
    readonly string _userData;
    public OneTimeTask(string data)
    {
        _userData = data;
    }

    public void Run(Timeout timeout)
    {
        Console.WriteLine($"{_userData} is fired at {DateTime.UtcNow.Ticks / 10000000L}");
    }
}


static void Main(string[] args)
{
    HashedWheelTimer timer = new HashedWheelTimer( tickDuration: TimeSpan.FromSeconds(1)
        , ticksPerWheel: 100000
        , maxPendingTimeouts: 0);

    timer.NewTimeout(new OneTimeTask("A"), TimeSpan.FromSeconds(5));
    timer.NewTimeout(new OneTimeTask("B"), TimeSpan.FromSeconds(4));
    var timeout = timer.NewTimeout(new OneTimeTask("C"), TimeSpan.FromSeconds(3));
    timer.NewTimeout(new OneTimeTask("D"), TimeSpan.FromSeconds(2));
    timer.NewTimeout(new OneTimeTask("E"), TimeSpan.FromSeconds(1));

    timeout.Cancel();

    timer.NewTimeout(new IntervalTimerTask(), TimeSpan.FromSeconds(5));
    Console.WriteLine($"{DateTime.UtcNow.Ticks / 10000000L} : Started");
    Console.ReadKey();
}

The output of the sample is something like alternate text is missing from this package README image

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 103 5/27/2026
1.0.0 101 5/19/2026