ManualResetEvent与Thread.Sleep

我实现了以下后台处理线程,其中JobsQueue

 static void WorkThread() { while (working) { var job; lock (Jobs) { if (Jobs.Count > 0) job = Jobs.Dequeue(); } if (job == null) { Thread.Sleep(1); } else { // [snip]: Process job. } } } 

这导致在输入作业和实际开始运行之间出现明显的延迟(一次性输入一批作业,每个作业只相对较小)。延迟并不是很大,但我开始考虑这个问题,并做了以下改变:

 static ManualResetEvent _workerWait = new ManualResetEvent(false); // ... if (job == null) { lock (_workerWait) { _workerWait.Reset(); } _workerWait.WaitOne(); } 

添加作业的线程现在锁定_workerWait并在完成添加作业时调用_workerWait.Set() 。 这个解决方案(貌似)立即开始处理工作,延迟完全消失。

我的问题部分是“为什么会发生这种情况?”,授予Thread.Sleep(int)可以比你指定的更长时间地睡眠,部分是“ ManualResetEvent如何实现这种性能水平?”。

编辑:因为有人询问了排队项目的function,现在它和目前的完整系统一起。

 public void RunTriggers(string data) { lock (this.SyncRoot) { this.Triggers.Sort((a, b) => { return a.Priority - b.Priority; }); foreach (Trigger trigger in this.Triggers) { lock (Jobs) { Jobs.Enqueue(new TriggerData(this, trigger, data)); _workerWait.Set(); } } } } static private ManualResetEvent _workerWait = new ManualResetEvent(false); static void WorkThread() { while (working) { TriggerData job = null; lock (Jobs) { if (Jobs.Count > 0) job = Jobs.Dequeue(); if (job == null) { _workerWait.Reset(); } } if (job == null) _workerWait.WaitOne(); else { try { foreach (Match m in job.Trigger.Regex.Matches(job.Data)) job.Trigger.Value.Action(job.World, m); } catch (Exception ex) { job.World.SendLineToClient("\r\n\x1B[32m -- {0} in trigger ({1}): {2}\x1B[m", ex.GetType().ToString(), job.Trigger.Name, ex.Message); } } } } 

这些事件是由OS / Kernel提供的内核原语,它是专门为这类事物设计的。 内核提供了一个边界,您可以在该边界上保证对同步很重要的primefaces操作(有些primefaces性也可以在用户空间中通过硬件支持完成)。

简而言之,当一个线程等待一个事件时,它会被置于该事件的等待列表中并被标记为不可运行。 当事件发出信号时,内核会唤醒等待列表中的那些并将它们标记为可运行,并且它们可以继续运行。 当事件发出信号时,线程可以立即唤醒,与长时间睡眠并且不时地重新检查状态,这自然是一个巨大的好处。

即使一毫秒真的很长,你也可以在那段时间处理成千上万的事件。 此外,时间分辨率传统上是10毫秒,所以睡眠时间不到10毫秒通常只会导致10毫秒的睡眠。 通过事件,可以立即唤醒并安排线程

首先锁定_workerWait是没有意义的,Event是一个系统(内核)对象,设计用于线程之间的信令(并在Win32 API中大量用于异步操作)。 因此,multithreading在没有额外同步的情况下设置或重置它是非常安全的。

至于你的主要问题,还需要看到将事情放在队列中的逻辑,以及关于每项工作完成了多少工作的一些信息(工作者线程花费更多时间处理工作或等待工作)。

可能最好的解决方案是使用对象实例锁定并使用Monitor.PulseMonitor.Wait作为条件变量。

编辑:有了要排队的代码视图,似乎答案#1116297说得对:1ms延迟太长,等待,因为许多工作项将非常快速地处理。

具有唤醒工作线程的机制的方法是正确的(因为没有具有阻塞出列操作的.NET并发队列)。 但是,不是使用事件,而是条件变量会更有效(在非竞争情况下,它不需要内核转换):

 object sync = new Object(); var queue = new Queue(); public void EnqueueTriggers(IEnumerable triggers) { lock (sync) { foreach (var t in triggers) { queue.Enqueue(t); } Monitor.Pulse(sync); // Use PulseAll if there are multiple worker threads } } void WorkerThread() { while (!exit) { TriggerData job = DequeueTrigger(); // Do work } } private TriggerData DequeueTrigger() { lock (sync) { if (queue.Count > 0) { return queue.Dequeue(); } while (queue.Count == 0) { Monitor.Wait(sync); } return queue.Dequeue(); } } 

Monitor.Wait将释放对参数的锁定,等待对锁定调用Pulse()PulseAll() ,然后重新进入锁定并返回。 需要重新检查等待条件,因为某些其他线程可能已从队列中读取该项。