C# 手动复位事件与线程休眠

C# 手动复位事件与线程休眠,c#,multithreading,sleep,manualresetevent,C#,Multithreading,Sleep,Manualresetevent,我实现了以下后台处理线程,其中作业是一个队列: 这在输入作业和实际开始运行作业之间产生了一个明显的延迟(一次输入一批作业,每个作业[相对]很小)。延迟不是很大,但我开始考虑这个问题,并做了以下更改: static ManualResetEvent _workerWait = new ManualResetEvent(false); // ... if (job == null) { lock (_workerWait) {

我实现了以下后台处理线程,其中
作业
是一个
队列

这在输入作业和实际开始运行作业之间产生了一个明显的延迟(一次输入一批作业,每个作业[相对]很小)。延迟不是很大,但我开始考虑这个问题,并做了以下更改:

static ManualResetEvent _workerWait = new ManualResetEvent(false);
// ...
    if (job == null)
    {
        lock (_workerWait)
        {
            _workerWait.Reset();
        }
        _workerWait.WaitOne();
    }
其中线程添加作业现在锁定
\u workerWait
,并在完成添加作业后调用
\u workerWait.Set()
。此解决方案(似乎)立即开始处理作业,延迟完全消失

我的问题一部分是“为什么会发生这种情况?”,假设
Thread.Sleep(int)
可以很好地睡眠超过您指定的时间,另一部分是“ManualResetEvent如何达到这种性能水平?”

编辑:因为有人问起排队项目的功能,这里是它,以及目前的整个系统

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);
            }
        }
    }
}

首先锁定
\u workerWait
是毫无意义的,事件是一个系统(内核)对象,用于在线程之间发送信号(在Win32 API中大量用于异步操作)。因此,多线程在不进行额外同步的情况下设置或重置它是非常安全的

至于您的主要问题,还需要了解将内容放入队列的逻辑,以及关于每个作业完成了多少工作的一些信息(工作线程是花费更多的时间处理工作还是等待工作)

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

编辑:从排队代码的角度来看,答案似乎是正确的:1毫秒的延迟太长,无法等待,因为许多工作项的处理速度非常快

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

对象同步=新对象();
var queue=新队列();
public void排队触发器(IEnumerable触发器){
锁定(同步){
foreach(触发器中的var t){
排队,排队(t);
}
Monitor.Pulse(sync);//如果有多个工作线程,则使用pulsell
}
}
void WorkerThread(){
当(!退出){
TriggerData作业=出列触发器();
//工作
}
}
private TriggerData出列触发器(){
锁定(同步){
如果(queue.Count>0){
return queue.Dequeue();
}
while(queue.Count==0){
监视器。等待(同步);
}
return queue.Dequeue();
}
}

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

事件是由OS/内核提供的内核原语,专门为此类事件而设计。内核提供了一个边界,在此边界上您可以保证原子操作,这对于同步非常重要(通过硬件支持,也可以在用户空间中实现某些原子性)

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


即使是一毫秒也是一段非常长的时间,在这段时间内你可以处理数千个事件。而且时间分辨率通常是10毫秒,所以睡眠少于10毫秒通常只会导致10毫秒的睡眠。通过事件,可以立即唤醒并调度线程

大多数作业将只匹配(预编译)正则表达式并退出(因为匹配失败)。数量取决于用户输入的数量,以及应用程序(它是一个网络应用程序)接收的数据量。它很可能在最大负载下以每秒几百次的速度达到峰值,可能多达一千次。我不确定是否有人会对代码队列项目感兴趣,但我现在正在编辑它,因为你问得很好:)我想我在某处读到监视器是lock(){}构造背后的后台?那么,你怎么能在同一个同步对象上使用lock()和Monitor呢?哦,等等,我刚刚阅读并理解了最后一段。更新的信息:10ms最小分辨率是XP和更早的东西,因为操作系统使用10ms的静态增量进行调度。我认为Vista,而且我知道Win7,使用了动态的“无滴答”时间片。使用Win7,我可以启动一个高分辨率计时器,发出一个睡眠(1),并且时间非常接近1ms,有时小于1ms。
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);
            }
        }
    }
}
object sync = new Object();
var queue = new Queue<TriggerData>();

public void EnqueueTriggers(IEnumerable<TriggerData> 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();
  }
}