Как использовать таймер остановки / запуска против SynchronizingObject и блокировки для System.Timers.Timer?

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

Верхнее примечание 2: Кстати, поскольку SO отличается от отрицательных голосов MSO или закрытых голосов, поощряет удаление вопроса, я не буду, в противном случае все ценные комментарии и ответы будут удалены. Здесь есть место, чтобы помочь и попытаться понять друг друга

Вот самая основная из 4 различных реализаций кодов linqpad. За исключением первого все остальные дают желаемый результат.

Можете ли вы объяснить детали для них?

Поскольку в моем приложении много таймеров, мне нужно управлять и синхронизировать полный код, который лучше всего использовать, и каковы преимущества / недостатки альтернативных решений.

Без ни SynchronizingObject, ни таймера остановки / запуска, ни блокировки

System.Timers.Timer timer2 = new System.Timers.Timer(100);
int i = 0;
void Main()
{
    timer2.Elapsed += PromptForSave;
    timer2.Start();
}

private void PromptForSave(Object source, System.Timers.ElapsedEventArgs e)
{
    i = i + 1;
    Thread.Sleep(new Random().Next(100, 1000));
    Console.WriteLine(i);
}

дает:

4 5 6 7 8 9 11 12 13 14 15 15 15 17 18 20 21 22


С SynchronizingObject:

void Main()
{
    timer2.Elapsed += PromptForSave;
    timer2.SynchronizingObject = new Synchronizer();
    timer2.Start();
}

private void PromptForSave(Object source, System.Timers.ElapsedEventArgs e)
{
    i = i + 1;
    Thread.Sleep(new Random().Next(100, 1000));
    Console.WriteLine(i);
}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20


С таймером Старт / Стоп

void Main()
{
    timer2.Elapsed += PromptForSave;    
    timer2.Start();
}

private void PromptForSave(Object source, System.Timers.ElapsedEventArgs e)
{
    timer2.Stop();
    i = i + 1;
    Thread.Sleep(new Random().Next(100, 1000));
    Console.WriteLine(i);
    timer2.Start();
}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20


наконец с замком

object lockForTimer = new object();
void Main()
{
    timer2.Elapsed += PromptForSave;    
    timer2.Start();
}

private void PromptForSave(Object source, System.Timers.ElapsedEventArgs e)
{
    lock(lockForTimer){
        i = i + 1;
        Thread.Sleep(new Random().Next(100, 1000));
        Console.WriteLine(i);
        timer2.Start();
    }
}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Синхронизатор выглядит так:

public class Synchronizer : ISynchronizeInvoke
{
    private Thread m_Thread;
    private BlockingCollection<Message> m_Queue = new BlockingCollection<Message>();

    public Synchronizer()
    {
        m_Thread = new Thread(Run);
        m_Thread.IsBackground = true;
        m_Thread.Start();
    }

    private void Run()
    {
        while (true)
        {
            Message message = m_Queue.Take();
            message.Return = message.Method.DynamicInvoke(message.Args);
            message.Finished.Set();
        }
    }

    public IAsyncResult BeginInvoke(Delegate method, object[] args)
    {
        Message message = new Message();
        message.Method = method;
        message.Args = args;
        m_Queue.Add(message);
        return message;
    }

    public object EndInvoke(IAsyncResult result)
    {
        Message message = result as Message;
        if (message != null)
        {
            message.Finished.WaitOne();
            return message.Return;
        }
        throw new ArgumentException("result");
    }

    public object Invoke(Delegate method, object[] args)
    {
        Message message = new Message();
        message.Method = method;
        message.Args = args;
        m_Queue.Add(message);
        message.Finished.WaitOne();
        return message.Return;
    }

    public bool InvokeRequired
    {
        get { return Thread.CurrentThread != m_Thread; }
    }

    private class Message : IAsyncResult
    {
        public Delegate Method = null;
        public object[] Args = null;
        public object Return = null;
        public object State = null;
        public ManualResetEvent Finished = new ManualResetEvent(false);

        public object AsyncState
        {
            get { return State; }
        }

        public WaitHandle AsyncWaitHandle
        {
            get { return Finished; }
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get { return Finished.WaitOne(0); }
        }
    }
}

1 ответ

Решение
  • 4-й раствор (lock) опасный. Так как Elapsed событие будет поднят на ThreadPool каждый раз, и вы можете заблокировать многие из них одновременно, это может привести к ThreadPool расти (со всеми вытекающими последствиями). Так что это плохое решение.

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

  • Второе решение поставит в очередь все действия и не пропустит их. Это потенциально опасно, когда время обработки действия (почти всегда) превышает интервал таймера. Очередь будет только расти, вызывая OutOfMemoryException в какой-то момент. Это решение похоже на "буфер кадров" в потоковом видео.

  • Первый должен быть удален, есть только проблемы с этим.

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

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