"Атомарное" изменение System.Threading.Timer

Допустим, у меня есть существующий экземпляр System.Threading.Timer, и я хотел бы вызвать на нем Change, чтобы вернуть время срабатывания:

var timer = new Timer(DelayCallback, null, 10000, Timeout.Infinite);
// ... (sometime later but before DelayCallback has executed)
timer.Change(20000, Timeout.Infinite);

Я использую этот таймер для выполнения «обратного вызова в режиме ожидания» после периода бездействия. («Бездействие» и «отсутствие активности» в данном случае являются условиями, определяемыми приложением ... детали не так уж и важны.) Каждый раз, когда я выполняю «действие», я хочу сбросить таймер, чтобы он всегда устанавливался. выстрелить через 10 секунд после этого.

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

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

В качестве альтернативы я всегда могу отслеживать ожидаемое время срабатывания (через DateTime.Now.AddSeconds (10)) и, если срабатывает исходный таймер, игнорировать его, проверяя DateTime.Now в обратном вызове. (Меня беспокоит то, что это может быть не на 100% надежным из-за таймера, использующего TimeSpan, и моей проверки с использованием DateTime ... это может не быть проблемой, но по какой-то причине мне это не совсем удобно ... )

Мои вопросы:

  1. Есть ли у меня хороший способ вызвать Timer.Change и узнать, удалось ли мне изменить его до того, как обратный вызов был поставлен в очередь в пул потоков? (Не думаю, но спросить не помешает ...)
  2. Кто-нибудь еще реализовал (что я называю) такой "таймер отката"? Если да, мне бы хотелось услышать, как вы решили эту проблему.

Этот вопрос носит несколько гипотетический характер, поскольку у меня уже есть несколько рабочих решений (на основе Dispose и на основе DateTime.Now) ... Меня в основном интересуют предложения, связанные с производительностью (поскольку я буду "отталкивать "Таймер ОЧЕНЬ часто").

Спасибо!

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
0
1 697
3

Ответы 3

На самом деле мне пришлось создать свой собственный класс «Тайминга» для созданной мной MMORPG. Он мог отслеживать более 100 000 «сущностей», у которых были таймеры для обработки ИИ и других задач. Из-за различных действий, которые можно было предпринять, мне пришлось бы на мгновение отложить мероприятие.

Мой урок по таймингу был полностью написан вручную, так что это не совсем то, что вы ищете. Но кое-что, что вы могли бы сделать, было бы похоже на решение, которое я придумал, - это сделать что-то вроде:

while (sleepyTime > 0)
{
    int temp = sleepyTime;
    sleepyTime = 0;
    Thread.Sleep(temp);
}

// here's where your actual code is.

Затем вы можете создать метод "Delay", который в основном просто рекламирует sleepyTime.

похоже, что вы действительно хотите, чтобы событие простоя приложения

System.Windows.Forms.Application.Idle

Я интерпретирую ваши вопросы как запрос на реализацию интерфейса IdleNotifier, указанного ниже. Также вы заявляете, что ActionOccured () должен быть быстрым.

public delegate void IdleCallback();
public interface IdleNotifier
{
    // Called by threadpool when more than IdleTimeSpanBeforeCallback 
    // has passed since last call on ActionOccured.
    IdleCallback Callback { set; }
    TimeSpan IdleTimeSpanBeforeCallback { set; }
    void ActionOccured();
}

Ниже я предлагаю реализацию с помощью System.Threading.Timer. Важные моменты по реализации:

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

Выполнение:

public class IdleNotifierTimerImplementation : IdleNotifier
{
    private readonly object SyncRoot = new object();
    private readonly Timer m_Timer;

    private IdleCallback m_IdleCallback = null;
    private TimeSpan m_IdleTimeSpanBeforeEvent = TimeSpan.Zero;

    // Null means there has been no action since last idle notification.
    private DateTime? m_LastActionTime = null;

    public IdleNotifierTimerImplementation()
    {
        m_Timer = new Timer(OnTimer);
    }

    private void OnTimer(object unusedState)
    {
        lock (SyncRoot)
        {
            if (m_LastActionTime == null)
            {
                m_Timer.Change(m_IdleTimeSpanBeforeEvent, TimeSpan.Zero);
                return;
            }
            TimeSpan timeSinceLastUpdate = DateTime.UtcNow - m_LastActionTime.Value;
            if (timeSinceLastUpdate > TimeSpan.Zero)
            {
                // We are no idle yet.
                m_Timer.Change(timeSinceLastUpdate, TimeSpan.Zero);
                return;
            }
            m_LastActionTime = null;
            m_Timer.Change(m_IdleTimeSpanBeforeEvent, TimeSpan.Zero);
        }
        if (m_IdleCallback != null)
        {
            m_IdleCallback();
        }
    }

    // IdleNotifier implementation below

    public void ActionOccured()
    {
        lock (SyncRoot)
        {
            m_LastActionTime = DateTime.UtcNow;
        }
    }

    public IdleCallback Callback
    {
        set
        {
            lock (SyncRoot)
            {
                m_IdleCallback = value;
            }
        }
    }

    public TimeSpan IdleTimeSpanBeforeCallback
    {
        set
        {
            lock (SyncRoot)
            {
                m_IdleTimeSpanBeforeEvent = value;
                // Run OnTimer immediately
                m_Timer.Change(TimeSpan.Zero, TimeSpan.Zero);
            }
        }
    }
}

В этом коде есть много простых улучшений производительности.

Если кого-то заинтересуют мои первые мысли по этому поводу, просто спросите меня.

Другие вопросы по теме