6

如何正确同步这个?目前有可能在完成SetData后调用,e.WaitOne()因此d可能已经设置为另一个值。我试图插入锁,但它导致了死锁。

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为 true,最后DoLongOperationWith_d可以设置为 true,所以如果SetData在此变量设置为 true 的情况下调用它可以返回?

4

3 回答 3

5

这是未经测试的,但使用基于 .net 的原语是一种优雅的方式:

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; 
             }
     }
}
于 2009-08-17T23:32:03.527 回答
2

这里有两种可能令人不安的情况。

1:

  • DoLongOperationWith_d(d) 完成。
  • 调用 SetData(),在 d 中存储一个新值。
  • e.WaitOne() 被调用,但由于已经设置了一个值,线程将永远等待。

如果这是你的担忧,我想你可以放松一下。从文档中,我们看到

如果线程在 AutoResetEvent 处于信号状态时调用 WaitOne,则线程不会阻塞。AutoResetEvent 立即释放线程并返回到无信号状态。

所以这不是问题。但是,根据调用 SetData() 的方式和时间,您可能正在处理更严重的问题

2:

  • 调用 SetData(),在 d 中存储一个新值并唤醒跑步者。
  • DoLongOperationWith_d(d) 开始。
  • 再次调用 SetData(),在 d 中存储一个新值。
  • 再次调用 SetData()!d 的旧值永远丢失;DoLongOperationWith_d() 永远不会被调用。

如果这是您的问题,解决它的最简单方法是并发队列。实施比比皆是。

于 2009-08-17T23:32:02.827 回答
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. 我认为无论如何你应该调查ThreadPool.QueueUserWorkItem

于 2009-08-17T23:55:27.147 回答