Могу ли я сделать этот класс TimerQueueTimer более производительным?

#c# #performance #timer

#c# #Производительность #таймер

Вопрос:

 using System;
using System.Threading;

internal class TimerQueueTimer : IDisposable
{
    public TimerQueueTimer(int interval, int msBeforeFirstCall)
    {
        this.interval = interval;
        this.msBeforeFirstCall = msBeforeFirstCall;
        this.callback = this.ticked;
        this.isTheFirstTick = true;
        this.isStopped = true;
    }

    public event EventHandler Ticked;

    public void Start()
    {
        if (!this.isStopped)
        {
            return;
        }

        this.isTheFirstTick = true;
        this.isStopped = false;
        Computer.ChangeTimerResolutionTo(1);
        NativeMethods.CreateTimerQueueTimer(
            out this.handle,
            IntPtr.Zero,
            this.callback,
            IntPtr.Zero,
            (uint)this.msBeforeFirstCall,
            (uint)this.interval,
            CallbackExecution.ExecuteInTimerThread);
    }

    public void Stop()
    {
        if (this.isStopped)
        {
            return;
        }

        NativeMethods.DeleteTimerQueueTimer(
            IntPtr.Zero,
            this.handle,
            IntPtr.Zero);
        Computer.ClearTimerResolutionChangeTo(1);
        this.isStopped = true;
    }

    public void Dispose()
    {
        this.Stop();
    }

    private void ticked(IntPtr parameterPointer, bool timerOrWaitFired)
    {
        if (this.isStopped)
        {
            return;
        }

        if (this.isTheFirstTick)
        {
            Thread.CurrentThread.Priority = ThreadPriority.Highest;
        }

        this.isTheFirstTick = false;
        var ticked = this.Ticked;
        if (ticked != null)
        {
            ticked(this, EventArgs.Empty);
        }
    }

    private IntPtr handle;
    private volatile bool isStopped;
    private volatile bool isTheFirstTick;
    private readonly WaitOrTimerDelegate callback;
    private readonly int interval;
    private readonly int msBeforeFirstCall;
}
  

(Примечание: Computer.ChangeTimerResolutionTo() и Computer.ClearTimerResolutionChangeTo() вызывают timeBeginPeriod и timeEndPeriod соответственно.)

Вопросы:

  1. Обратный вызов выполняется в потоке таймера, а не в потоке ThreadPool. Это нормально, если функция обратного вызова выполняется быстро, верно?
  2. Делает ли установка приоритета потока обратного вызова (и, следовательно, потока таймера) наивысшим что-нибудь с точки зрения производительности?
  3. Было бы лучше установить интервал таймера в 1 мс и подсчитывать такты, повышая Ticked if tickCount % interval == 0 ? Является ли таймер с меньшим интервалом более точным?
  4. Есть ли какая-либо причина, по которой это может быть менее точным и / или выверенным, чем аналогично созданный timeSetEvent таймер?

Причина, по которой я спрашиваю, заключается в том, что мы сталкиваемся с проблемами, когда обратный вызов таймера иногда задерживается на ~ 50 мс, когда система находится под большой нагрузкой. По сравнению с тем, когда мы использовали ранее timeSetEvent , казалось, что это происходило реже — хотя это может быть просто иллюзией. Я знаю, что Windows не является детерминированной, поэтому я могу сделать не так много. Тем не менее, я хочу убедиться, что я сделал все, что мог, чтобы сделать это как можно более высокоприоритетным. Могу ли я сделать что-нибудь еще?

Комментарии:

1. К вашему сведению: проверять, нужно ли ticked == null сначала только для прямого вызова Ticked события, на самом деле не лучший ход. Обычно весь смысл локальной переменной заключается в том, чтобы избежать состояния гонки.

Ответ №1:

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

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

Когда поток таймера пробуждается по таймауту, он запускает новый поток из пула потоков, который вызывает обратный вызов.

Здесь у меня есть реализация очереди таймера, она недостаточно хорошо протестирована, но вы можете посмотреть, поможет ли это.

Комментарии:

1. @ruffp было бы здорово, если бы вы могли убрать -ve point, если считаете, что я решил вашу проблему 🙂

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