2009-08-17 2 views
4

Как правильно синхронизировать это? На данный момент возможно, что SetData вызывается после завершения e.WaitOne(), поэтому d может быть уже установлен на другое значение. Я попытался вставить замки, но это привело к тупиковой ситуации.Вопрос о выпуске C# с AutoResetEvent

AutoResetEvent e = new AutoResetEvent(false); 

public SetData(MyData d) 
{ 
    this.d=d; 
    e.Set(); // notify that new data is available 
} 

// This runs in separate thread and waits for d to be set to a new value 
void Runner() 
{  
    while (true) 
    { 
     e.WaitOne(); // waits for new data to process 
     DoLongOperationWith_d(d); 
    } 
} 

будет лучшим решением будет ввести новую булеву переменную dataAlreadyBeenSetAndWaitingToBeProcessed, которая устанавливается в SetData к истине и в конце DoLongOperationWith_d она может быть установлена ​​истиной, так что если SetData вызывается с этим переменным значением ИСТИНЫ он может просто вернуться?

ответ

3

Это не проверено, но является элегантным способом сделать это с помощью.чистые примитивы на основе:

class Processor<T> { 
    Action<T> action; 
    Queue<T> queue = new Queue<T>(); 

    public Processor(Action<T> action) { 
     this.action = action; 
     new Thread(new ThreadStart(ThreadProc)).Start(); 
    } 

    public void Queue(T data) { 
     lock (queue) { 
      queue.Enqueue(data); 
      Monitor.Pulse(queue); 
     }    
    } 

    void ThreadProc() { 
     Monitor.Enter(queue); 
     Queue<T> copy; 

     while (true) {     
      if (queue.Count == 0) { 
       Monitor.Wait(queue); 
      } 

      copy = new Queue<T>(queue); 
      queue.Clear(); 
      Monitor.Exit(queue); 

      foreach (var item in copy) { 
       action(item); 
      } 

      Monitor.Enter(queue); 
     } 
    } 
} 

Пример программы:

class Program { 

    static void Main(string[] args) { 

     Processor<int> p = new Processor<int>((data) => { Console.WriteLine(data); }); 
     p.Queue(1); 
     p.Queue(2); 

     Console.Read(); 

     p.Queue(3); 
    } 
} 

Это версия без очереди, версия очереди может быть предпочтительным:

object sync = new object(); 
AutoResetEvent e = new AutoResetEvent(false); 
bool pending = false; 

public SetData(MyData d) 
{ 
    lock(sync) 
    { 
     if (pending) throw(new CanNotSetDataException()); 

     this.d=d; 
     pending = true; 
    } 

    e.Set(); // notify that new data is available 
} 

void Runner() // this runs in separate thread and waits for d to be set to a new value 
{ 

    while (true) 
    { 

      e.WaitOne(); // waits for new data to process 
      DoLongOperationWith_d(d); 
      lock(sync) 
      { 
       pending = false; 
      } 
    } 
} 
+0

@ Spencer Ruport: Что? Если в ожидании установлено значение true при первом вызове SetData, тогда он будет бросать второй раз. Я уверен, что есть какой-то способ нарушить это, но я не думаю, что это с той последовательностью, которую вы описали. – Sean

+0

, но this.d не может быть задан, если ожидающим является false. –

+0

Мой плохой. Я не видел 'if (pending)' там. –

2

Здесь есть два возможных тревожных сценария.

1:

  • DoLongOperationWith_d (д) заканчивается.
  • Вызывается SetData(), сохраняя новое значение в d.
  • e.WaitOne() вызывается, но поскольку значение уже установлено, поток ожидает навсегда.

Если это ваша забота, я думаю, вы можете расслабиться. Из documentation, мы видим, что

Если поток вызывает WaitOne в то время как AutoResetEvent находится в сигнальном состоянии, поток не блокируется. AutoResetEvent немедленно освобождает поток и возвращается в состояние без сигнализации.

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

2:

  • SetData() вызывается, сохраняя новое значение в d и просыпаться бегуна ,
  • Начало DoLongOperationWith_d (d).
  • SetData() вызывается снова, сохраняя новое значение в d.
  • SetData() вызывается снова! Старое значение d теряется навсегда; DoLongOperationWith_d() никогда не будет вызываться на него.

Если это ваша проблема, самым простым способом решить эту проблему является одновременная очередь. Реализации много.

1

Вы можете использовать 2 события,

AutoResetEvent e = new AutoResetEvent(false); 
AutoResetEvent readyForMore = new AutoResetEvent(true); // Initially signaled 

public SetData(MyData d) 
{ 
    // This will immediately determine if readyForMore is set or not. 
    if(readyForMore.WaitOne(0,true)) { 
    this.d=d; 
    e.Set(); // notify that new data is available 
    } 
    // you could return a bool or something to indicate it bailed. 
} 

void Runner() // this runs in separate thread and waits for d to be set to a new value 
{ 

    while (true) 
    { 

      e.WaitOne(); // waits for new data to process 
      DoLongOperationWith_d(d); 
      readyForMore.Set(); 
    } 
} 

Одна из вещей вы можете сделать это, так как SetData принимает тайм-аут и передает это значение в WaitOne. Я думаю, однако вы shoudl расследовать ThreadPool.QueueUserWorkItem.

+0

Проблема заключается в том, что SetData будет заблокирован, как только он начнет обработку. –

+0

Очередь явно превосходит. Я просто попытался ответить на вопрос точно так же, как и спросил. –

Смежные вопросы