首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ManualResetEvent对Thread.Sleep

ManualResetEvent对Thread.Sleep
EN

Stack Overflow用户
提问于 2009-07-12 15:34:03
回答 2查看 18.6K关注 0票数 13

我实现了以下后台处理线程,其中Jobs是一个Queue<T>

代码语言:javascript
复制
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.
        }
    }
}

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

代码语言:javascript
复制
static ManualResetEvent _workerWait = new ManualResetEvent(false);
// ...
    if (job == null)
    {
        lock (_workerWait)
        {
            _workerWait.Reset();
        }
        _workerWait.WaitOne();
    }

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

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

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

代码语言:javascript
复制
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);
            }
        }
    }
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2009-07-12 15:58:48

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

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

即使是毫秒也是一段很长的时间,你可以在这段时间内处理成千上万的事件。另外,传统的时间分辨率是10毫秒,所以睡眠少于10毫秒通常只会导致10毫秒的睡眠。对于事件,线程可以被唤醒并立即调度。

票数 19
EN

Stack Overflow用户

发布于 2009-07-12 15:44:36

第一次锁定_workerWait是没有意义的,事件是一个系统(内核)对象,用于在线程之间发送信令(并在异步操作的Win32 API中大量使用)。因此,对于多个线程来说,在没有额外同步的情况下设置或重置它是非常安全的。

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

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

编辑:从队列代码的角度来看,答案#1116297似乎是对的: 1ms的延迟太长,不能等待,因为许多工作项的处理速度都非常快。

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

代码语言:javascript
复制
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();
  }
}

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

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1116249

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档