public ProtocolEventDebouncer(ITimerFactory timerFactory, TimeSpan delay, TimeSpan maxDelay)
        {
            _timer = new SlidingWindowExclusiveTimer(timerFactory, delay, maxDelay, Process);

            // delegate for this block can't be async otherwise the shared exclusive scheduler is pointless
            _enqueueBlock = new ActionBlock <Tuple <TaskCompletionSource <bool>, ProtocolEvent, bool> >(tuple =>
            {
                try
                {
                    if (tuple.Item2 is KeyspaceProtocolEvent keyspaceEvent)
                    {
                        KeyspaceEventReceived(keyspaceEvent, tuple.Item3, tuple.Item1);
                    }
                    else
                    {
                        MainEventReceived(tuple.Item2, tuple.Item3, tuple.Item1);
                    }
                }
                catch (Exception ex)
                {
                    ProtocolEventDebouncer.Logger.Error("Unexpected exception in Protocol Event Debouncer while receiving events.", ex);
                }
            }, new ExecutionDataflowBlockOptions
            {
                EnsureOrdered          = true,
                MaxDegreeOfParallelism = 1,
                TaskScheduler          = _timer.ExclusiveScheduler
            });

            _processQueueBlock = new ActionBlock <EventQueue>(async queue =>
            {
                try
                {
                    await ProtocolEventDebouncer.ProcessQueue(queue).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    ProtocolEventDebouncer.Logger.Error("Unexpected exception in Protocol Event Debouncer while processing queue.", ex);
                }
            }, new ExecutionDataflowBlockOptions
            {
                EnsureOrdered          = true,
                MaxDegreeOfParallelism = 1
            });
        }