private async Task WorkerRoutine(CancellationToken token)
        {
            if (delayFirstIteration)
            {
                await DelaySafe(period(), token).ConfigureAwait(false);
            }

            while (!token.IsCancellationRequested)
            {
                var budget = TimeBudget.StartNew(period(), TimeSpan.FromMilliseconds(1));

                try
                {
                    await action(token, budget.Remaining).ConfigureAwait(false);
                }
                catch (OperationCanceledException e) when(e.CancellationToken == token && token.IsCancellationRequested)
                {
                    return;
                }
                catch (Exception error)
                {
                    errorHandler(error);
                }

                await DelaySafe(budget.Remaining, token).ConfigureAwait(false);
            }
        }
        public PeriodicalAction(
            [NotNull] Action <TimeSpan> action,
            [NotNull] Action <Exception> errorHandler,
            [NotNull] Func <TimeSpan> period,
            bool delayFirstIteration = false)
        {
            if (action == null)
            {
                throw new ArgumentNullException(nameof(action));
            }

            if (period == null)
            {
                throw new ArgumentNullException(nameof(period));
            }

            this.errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler));

            stopEvent = new AutoResetEvent(false);

            workerRoutine = () =>
            {
                if (delayFirstIteration)
                {
                    stopEvent.WaitOne(period());
                }

                while (IsRunning)
                {
                    var actionBudget = TimeBudget.StartNew(period());

                    try
                    {
                        action(actionBudget.Remaining);
                    }
                    catch (Exception error)
                    {
                        errorHandler(error);
                    }

                    stopEvent.WaitOne(actionBudget.Remaining);
                }
            };
        }