void OnNext()
        {
            var work_item = (InvocationThrottleWorkItem)null;

            lock (Sync)
            {
                if (CurrentWorkItem != null)
                {
                    Debug.WriteLine($"On Next, Current not null, exit {CurrentWorkItem.Id}");
                    return;
                }

                if (PendingWorkItem == null)
                {
                    Debug.WriteLine($"On Next, No Pending, exit");
                    return;
                }

                var old_current = CurrentWorkItem;

                CurrentWorkItem = PendingWorkItem;
                PendingWorkItem = null;

                work_item = CurrentWorkItem;

                work_item.CancellationTokenSource = new CancellationTokenSource();
            }

            Debug.WriteLine($"On Next, Task.StartNew");

            work_item.Task = Task.Factory.StartNew((_wi) =>
            {
                var wi = (InvocationThrottleWorkItem)_wi;
                var ct = wi.CancellationTokenSource.Token;

                bool success = false;

                try
                {
                    if (ct.IsCancellationRequested)
                    {
                        Debug.WriteLine($"Invoke, canceled before started {wi.Id}");
                    }
                    else
                    {
                        Debug.WriteLine($"Invoke, {wi.Id}");
                        wi.Action(wi.State, wi, ct);
                        Debug.WriteLine($"Invoke success, {wi.Id}");
                        success = true;
                    }
                }
                catch (OperationCanceledException)
                {
                    Debug.WriteLine($"Invoke, canceled {wi.Id}");
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"Invoke, exception {wi.Id}");
                }

                lock (Sync)
                {
                    if (ct.IsCancellationRequested)
                        success = false;

                    var utc_now = DateTime.UtcNow;

                    var old_current = CurrentWorkItem;

                    CurrentWorkItem = null;

                    if (success)
                        wi.CompleteTimeUtc = DateTime.UtcNow;

                    // check if there is a task pending
                    if (PendingWorkItem != null)
                    {
                        // it is, but within min,
                        // or last call failed
                        // reset timer to start from now
                        if (success && utc_now - PendingWorkItem.RequestWindowTimeUTC < Min)
                        {
                            Debug.WriteLine($"Invoke, pending should be triggered by timer, old current: {wi.Id}, pending: {PendingWorkItem.Id}");
                            // nothing to do here, timer will take care of it
                            return;
                        }
                        else
                        {
                            // last call failed
                            // or success but pending is waiting > min

                            // make sure to update window, if current failed
                            if (!success)
                            {
                                if (PendingWorkItem.RequestWindowTimeUTC > old_current.RequestWindowTimeUTC)
                                    PendingWorkItem.RequestWindowTimeUTC = old_current.RequestWindowTimeUTC;

                                // total window past Max waiting period, invoke
                                if (Max != null && PendingWorkItem.RequestTimeUTC - PendingWorkItem.RequestWindowTimeUTC >= Max)
                                {
                                    // past Max waiting time, invoke and ensure runs to completion
                                    PendingWorkItem.MustRunToCompletion = true;
                                }

                                // invoke pending action immediately, since its been waiting long enough
                                Debug.WriteLine($"Invoke, Calling On Next, old current: {wi.Id}, pending: {PendingWorkItem.Id}");
                                OnNext();
                                return;
                            }
                        }
                    }
                    else
                    {
                        // no work items pending
                        // nothing else to do here
                        Debug.WriteLine($"Invoke, no more pending items, do nothing {wi.Id}");
                    }
                }

            }, work_item, CancellationToken.None, TaskCreationOptions.None, Scheduler);
        }
        public void InvokeAsync(Action<object, InvocationThrottleWorkItem, CancellationToken> cancellableActionWithState, object state)
        {
            if (cancellableActionWithState == null)
                throw new ArgumentNullException(nameof(cancellableActionWithState));

            var invocation_time = DateTime.UtcNow;

            var current_item = CurrentWorkItem;
            if (current_item != null && current_item.RequestTimeUTC >= invocation_time)
                return;

            lock (Sync)
            {
                var wi = new InvocationThrottleWorkItem();
                wi.RequestTimeUTC = invocation_time;
                wi.State = state;
                wi.Action = cancellableActionWithState;

                Debug.WriteLine($"Invoke Async {wi.Id}");

                // Pending request alredy exists
                if (PendingWorkItem != null)
                {
                    // update window request time (to be able to check for max later)
                    wi.RequestWindowTimeUTC = PendingWorkItem.RequestWindowTimeUTC;
                    wi.MustRunToCompletion = PendingWorkItem.MustRunToCompletion;

                    // total window past Max waiting period, invoke
                    if (Max != null && invocation_time - wi.RequestWindowTimeUTC >= Max)
                    {
                        // past Max waiting time, invoke and ensure runs to completion
                        wi.MustRunToCompletion = true;
                        PendingWorkItem = wi;
                        Debug.WriteLine($"Invoke Async, must run to completion {wi.Id}");
                        OnNextIfReady();
                        return;
                    }

                    // within min distance of last pending request
                    if (invocation_time - PendingWorkItem.RequestTimeUTC < Min)
                    {
                        PendingWorkItem = wi;

                        Debug.WriteLine($"Invoke Async, Reset Timer, RequestWindow: {wi.RequestTimeUTC}, req-window: {(wi.RequestTimeUTC - wi.RequestWindowTimeUTC).TotalMilliseconds} ms, {wi.Id}");
                        ResetMinTimer();

                        return;
                    }
                    else // passed min distance, invoke
                    {
                        PendingWorkItem = wi;
                        Debug.WriteLine($"Invoke Async, passed min distance {wi.Id}");
                        OnNextIfReady();
                        return;
                    }
                }
                else // there's no pending request
                {
                    wi.RequestWindowTimeUTC = wi.RequestTimeUTC;
                    PendingWorkItem = wi;
                    Debug.WriteLine($"Invoke Async, no pending requests {wi.Id}");
                    OnNextIfReady();
                }
            }
        }
        public void Cancel(bool forced = true)
        {
            lock(Sync)
            {
                if(CurrentWorkItem != null)
                {
                    if (forced || !CurrentWorkItem.MustRunToCompletion)
                    {
                        CurrentWorkItem.CancellationTokenSource.Cancel();
                    }
                }

                if (PendingWorkItem != null)
                    PendingWorkItem = null;
            }
        }