Производитель-потребитель с вариацией - Как синхронизировать с потоком сигнал / ожидание? - PullRequest
4 голосов
/ 24 ноября 2010

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

ThreadPool.QueueUserWorkItem (() => 
{
    Thread.Sleep (5000);
    Foo (); // Call is to be executed after sometime
});

Поэтому я создал отдельный класс планировщика, который запускается в своем собственном потоке и выполняет эти события.У меня есть 2 функции, которые получают доступ к общей очереди из отдельных потоков.Я бы использовал блокировку, но так как один из потоков должен находиться в режиме ожидания, я не знал, как снять блокировку.

class Scheduler
{
    SortedDictionary <DateTime, Action> _queue;
    EventWaitHandle _sync;

    // Runs on its own thread
    void Run ()
    {
        while (true)
        {
            // Calculate time till first event
            // If queue empty, use pre-defined value
            TimeSpan timeDiff = _queue.First().Key - DateTime.Now;

            // Execute action if in the next 100ms
            if (timeDiff < 100ms)
                ...
            // Wait on event handle for time
            else
                _sync.WaitOne (timeDiff);
        }
    }

    // Can be called by any thread
    void ScheduleEvent (Action action, DataTime time)
    {
        _queue.Add (time, action);
        // Signal thread to wake up and check again
        _sync.Set ();
    }
}

Проблема в том, что я неУверен, как синхронизировать доступ к очереди между двумя функциями.Я не могу использовать монитор или мьютекс, потому что Run () будет спать-ждать, вызывая тем самым тупик.Какой механизм синхронизации использовать здесь?(Если существует механизм для атомарного запуска процесса ожидания и немедленного снятия блокировки, это может решить мою проблему) Как я могу убедиться в отсутствии состояния гонки? Является ли это разновидностью проблемы потребителя производителя, или есть более актуальное описание проблемы синхронизации?Хотя это несколько ориентировано на C #, я был бы рад услышать общее решение этого вопроса.Спасибо!

Ответы [ 4 ]

3 голосов
/ 24 ноября 2010

ОК, возьмите 2 с монитором / пульс.

    void Run ()    
    {
        while (true)
        {
            Action doit = null;

            lock(_queueLock)
            {
                while (_queue.IsEmpty())
                    Monitor.Wait(_queueLock);

                TimeSpan timeDiff = _queue.First().Key - DateTime.Now;
                if (timeDiff < 100ms)
                    doit = _queue.Dequeue();
            }

            if (doit != null)
                ; //execute doit
            else
             _sync.WaitOne (timeDiff);  
        }
    }


void ScheduleEvent (Action action, DataTime time)
{
    lock (_queueLock)
    {
        _queue.Add(time, action);
        // Signal thread to wake up and check again
        _sync.Set ();
        if (_queue.Count == 1)
             Monitor.Pulse(_queuLock);
    }
}
3 голосов
/ 24 ноября 2010

Проблема легко решается, убедитесь, что WaitOne находится за пределами блокировки.

  //untested
  while (true)
  {
      Action doit = null;

      // Calculate time till first event
      // If queue empty, use pre-defined value
      lock(_queueLock)
      {
         TimeSpan timeDiff = _queue.First().Key - DateTime.Now;
         if (timeDiff < 100ms)
            doit = _queue.Dequeue();
      }
      if (doit != null)
        // execute it
      else
         _sync.WaitOne (timeDiff);
  }

_queueLock является частным вспомогательным объектом.

2 голосов
/ 24 ноября 2010

Поскольку ваша цель - запланировать задачу по истечении определенного периода времени, почему бы не использовать System.Threading.Timer? Он не требует выделения потока для планирования и использует ОС для пробуждения рабочего потока. Я использовал это (удалил некоторые комментарии и другие функции сервиса таймера):

public sealed class TimerService : ITimerService
{
    public void WhenElapsed(TimeSpan duration, Action callback)
    {
        if (callback == null) throw new ArgumentNullException("callback");

        //Set up state to allow cleanup after timer completes
        var timerState = new TimerState(callback);
        var timer = new Timer(OnTimerElapsed, timerState, Timeout.Infinite, Timeout.Infinite);
        timerState.Timer = timer;

        //Start the timer
        timer.Change((int) duration.TotalMilliseconds, Timeout.Infinite);
    }

    private void OnTimerElapsed(Object state)
    {
        var timerState = (TimerState)state;
        timerState.Timer.Dispose();
        timerState.Callback();
    }

    private class TimerState
    {
        public Timer Timer { get; set; }

        public Action Callback { get; private set; }

        public TimerState(Action callback)
        {
            Callback = callback;
        }
    }
}
1 голос
/ 24 ноября 2010

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

    void Run()
    {
      while(true)
         lock(this)
         {
            int timeToSleep = getTimeToSleep() //check your list and return a value
            if(timeToSleep <= 100) 
                action...
            else
            {

               int currTime = Datetime.Now;
               int currCount = yourList.Count;
               try{
               do{
                 Monitor.Wait(this,timeToSleep);

                 if(Datetime.now >= (tomeToSleep + currtime))
                      break; //time passed

                 else if(yourList.Count != currCount)
                    break; //new element added go check it
                 currTime = Datetime.Now;
               }while(true);
            }
            }catch(ThreadInterruptedException e)
            {
                //do cleanup code or check for shutdown notification
            }
         }
      }
    }

void ScheduleEvent (Action action, DataTime time)
{
    lock(this)
    {
       yourlist.add ...
       Monitor.Pulse(this);

}}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...