Неповторно используемый таймер C#

Я пытаюсь вызвать метод f() каждый t время, но если предыдущий вызов f() еще не закончился, ожидайте, пока это не закончено.

Я считал немного о доступных таймерах, но не мог найти хороший способ сделать то, что я хочу, сохраняю для того, чтобы вручную записать все это. Любая справка о том, как достигнуть этого, будет цениться, хотя я боюсь, что не смог находить простое решение с помощью таймеров.

Разъясниться, если t одна секунда, и f() выполняет произвольные продолжительности, которые я записал ниже, затем:

Step  Operation    Time taken
1     wait         1s
2     f()          0.6s
3     wait         0.4s (because f already took 0.6 seconds)
4     f()          10s
5     wait         0s (we're late)
6     f()          0.3s
7     wait         0.7s (we can disregard the debt from step 4)

Заметьте, что природа этого таймера - это f() не должно будет быть безопасным относительно повторной входимости, и пул потоков размера 1 достаточно здесь.

10
задан Oak 27 February 2018 в 14:36
поделиться

7 ответов

Вы можете просто использовать переменную «глобального» уровня (или, что более вероятно, общедоступное свойство в том же классе, что и f () ), которая возвращает истину, если f () равно уже работает.

Итак, если f () находится в классе с именем TimedEvent , первое, что сделает f () , это установит Running true

Таким образом, ваш таймер срабатывает каждую секунду, а затем запускает синхронизированное событие, если оно еще не запущено
if (! timedEvent.Running) timedEvent.f ()

Вы прокомментировали, что f () не повторится немедленно, если это займет больше времени, чем интервал таймера. Это справедливый момент. Я бы, вероятно, включил подобную логику в f () , чтобы Running оставалось верным. Это будет выглядеть примерно так:

public void f(int t) // t is interval in seconds
{
   this.running = true;

   Stopwatch stopWatch = new Stopwatch();
   stopWatch.Start();

   do
   {
       stopwatch.Reset();

       // Do work here

   } while (stopWatch.Elapsed.Seconds > t); // repeat if f() took longer than t

   this.running = false;   
}
8
ответ дан 3 December 2019 в 16:51
поделиться

Используйте System.Threading.Timer. Инициализируйте его с периодом Timeout.Infinite, чтобы он действовал как одноразовый таймер. Когда f() завершится, вызовите его метод Change(), чтобы перезарядить его снова.

12
ответ дан 3 December 2019 в 16:51
поделиться

Простой поток - самый простой способ достичь этого. Вы все еще не будете уверены, что вас вызовут "точно", когда вы хотите, но это должно быть близко..... Также вы можете решить, хотите ли вы пропустить вызовы, которые должны произойти, или попытаться наверстать упущенное... Вот простая вспомогательная процедура для создания потока.

public static Thread StartTimer(TimeSpan interval, Func<bool> operation)
{
    Thread t = new Thread(new ThreadStart(
        delegate()
        {
            DateTime when = DateTime.Now;
            TimeSpan wait = interval;
            while (true)
            {
                Thread.Sleep(wait);
                if (!operation())
                    return;
                DateTime dt = DateTime.Now;
                when += interval;
                while (when < dt)
                    when += interval;
                wait = when - dt;
            }
        }
    ));
    t.IsBackground = true;
    t.Start();
    return t;
}
0
ответ дан 3 December 2019 в 16:51
поделиться

Вы не можете заставить таймер звонить вам через точно запланированные интервалы. Все таймеры перезвонят вам не раньше запрошенного времени.

Некоторые таймеры лучше других (например, Windows.Forms.Timer очень нестабильный и ненадежный по сравнению с System.Threading.Timer)

Чтобы остановить повторный вызов таймера, можно остановить таймер, пока ваш метод работает. (В зависимости от типа используемого вами таймера вы либо останавливаете его и запускаете снова, когда ваш обработчик завершает работу, либо с помощью некоторых таймеров вы можете запросить один обратный вызов, а не повторять обратные вызовы, поэтому каждое выполнение вашего обработчика просто ставит в очередь следующий вызов) .

Чтобы обеспечить относительно равномерное распределение времени между этими вызовами, вы можете записать время с момента последнего выполнения обработчика и использовать его для расчета задержки до следующего события. например Если вы хотите, чтобы вас вызывали один раз в секунду и ваш таймер завершил обработку на 1.02 с, то вы можете настроить следующий обратный вызов таймера на длительность 0,98 с, чтобы учесть тот факт, что вы уже «израсходовали» часть следующего. второй во время обработки.

1
ответ дан 3 December 2019 в 16:51
поделиться

Вы можете использовать таймер без перезапуска, а затем вручную перезапустить таймер после завершения метода.

Обратите внимание, что это приведет к тому, что время будет несколько отличаться от того, о котором вы просите. (Между вызовами всегда будет промежуток времени t )

Вы можете решить эту проблему, установив интервал на lastTick + t - Now и немедленно запустив метод, если это <= 0 .

Остерегайтесь условий гонки, если вам нужно остановить таймер.

3
ответ дан 3 December 2019 в 16:51
поделиться

Как насчет использования делегатов для метода f (), постановки их в очередь в стек и извлечения стека по мере завершения работы каждого делегата? Конечно, вам еще понадобится таймер.

0
ответ дан 3 December 2019 в 16:51
поделиться

Простое решение:

private class Worker : IDisposable
{
    private readonly TimeSpan _interval;
    private WorkerContext _workerContext;

    private sealed class WorkerContext
    {
        private readonly ManualResetEvent _evExit;
        private readonly Thread _thread;
        private readonly TimeSpan _interval;

        public WorkerContext(ParameterizedThreadStart threadProc, TimeSpan interval)
        {
            _evExit = new ManualResetEvent(false);
            _thread = new Thread(threadProc);
            _interval = interval;
        }

        public ManualResetEvent ExitEvent
        {
            get { return _evExit; }
        }

        public TimeSpan Interval
        {
            get { return _interval; }
        }

        public void Run()
        {
            _thread.Start(this);
        }

        public void Stop()
        {
            _evExit.Set();
        }

        public void StopAndWait()
        {
            _evExit.Set();
            _thread.Join();
        }
    }

    ~Worker()
    {
        Stop();
    }

    public Worker(TimeSpan interval)
    {
        _interval = interval;
    }

    public TimeSpan Interval
    {
        get { return _interval; }
    }

    private void DoWork()
    {
        /* do your work here */
    }

    public void Start()
    {
        var context = new WorkerContext(WorkThreadProc, _interval);
        if(Interlocked.CompareExchange<WorkerContext>(ref _workerContext, context, null) == null)
        {
            context.Run();
        }
        else
        {
            context.ExitEvent.Close();
            throw new InvalidOperationException("Working alredy.");
        }
    }

    public void Stop()
    {
        var context = Interlocked.Exchange<WorkerContext>(ref _workerContext, null);
        if(context != null)
        {
            context.Stop();
        }
    }

    private void WorkThreadProc(object p)
    {
        var context = (WorkerContext)p;
        // you can use whatever time-measurement mechanism you want
        var sw = new System.Diagnostics.Stopwatch();
        int sleep = (int)context.Interval.TotalMilliseconds;
        while(true)
        {
            if(context.ExitEvent.WaitOne(sleep)) break;

            sw.Reset();
            sw.Start();

            DoWork();

            sw.Stop();

            var time = sw.Elapsed;
            if(time < _interval)
                sleep = (int)(_interval - time).TotalMilliseconds;
            else
                sleep = 0;
        }
        context.ExitEvent.Close();
    }

    public void Dispose()
    {
        Stop();
        GC.SuppressFinalize(this);
    }
}
0
ответ дан 3 December 2019 в 16:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: