【问题标题】:Is this Background Thread Queue a performant implementation?这个后台线程队列是一个高性能的实现吗?
【发布时间】:2012-02-18 17:22:39
【问题描述】:

具体来说,我想知道:

ManualResetEvent 处于等待状态时会消耗资源吗?上下文切换的性能下降是否适用于处于等待状态的线程?

如果我可以选择使用多个各自工作较少的BackgroundThreadQueue,或者一个工作较多的BackgroundThreadQueue,并且我选择使用多个......等待线程队列会在它们不做任何事情时影响进程性能吗?

我应该在 C# 中使用更好的 FIFO 线程队列,还是使用不同的锁定策略?

欢迎提出任何建议。

/// <summary>
/// This class is responsible for peforming actions in a FIFO order on a 
/// background thread. When it is constructed, a background thread is created 
/// and a manual reset event is used to trigger actions to be performed when 
/// a new action is enqueued, or when one finishes. There is a ShuttingDown 
/// flag that is set by calling code when it is time to destroy the thread, 
/// and a QueueIsEmpty event is fired whenever the queue finishes executing 
/// the last action.
/// </summary>
public class BackgroundThreadQueue : IBackgroundThreadQueue
{
    #region Fields

    private readonly Queue<Action> queueOfActions = new Queue<Action>();
    readonly ManualResetEvent resetEvent;
    private bool shuttingDown;
    private bool readyToShutdown;
    private readonly object lockObject = new object();
    private string queuName;

    #endregion Fields

    #region Events

    /// <summary>
    /// Occurs when the BackgroundThreadQueue is empty, and ready to shut down.
    /// </summary>
    public event EventHandler IsReadyToShutdown;

    #endregion Events

    #region Constructor

    public BackgroundThreadQueue(string threadName)
    {
        this.resetEvent = new ManualResetEvent(false);
        queuName = threadName;
        StartThread();
    }

    #endregion Constructor

    #region Public Methods

    public void ClearQueue()
    {
        lock (lockObject)
        {
            queueOfActions.Clear();
        }
        resetEvent.Set();
    }

    /// <summary>
    /// Enqueues an action, and calls set on the manual reset event to trigger 
    /// the action to be performed (if no action is currently being performed, 
    /// the one just enqueued will be done immediately, if an action is already 
    /// being performed, then the one just enqueued will have to wait its turn).
    /// </summary>
    public void EnqueueAction(Action actionToEnqueue)
    {
        if (actionToEnqueue == null)
        {
            throw new ArgumentNullException("actionToEnqueue");
        }

        bool localReadyToShutDown = false;
        lock (lockObject)
        {
            queueOfActions.Enqueue(actionToEnqueue);

            if(this.readyToShutdown)
            {
                localReadyToShutDown = true;
                this.readyToShutdown = false;
            }
        }

        //if this instance is ready to shut down...and we just enqueued a 
        //new action...we can't shut down now...
        if (localReadyToShutDown)
        {
            StartThread();
        }
        resetEvent.Set();
    }

    #endregion Public Methods

    #region Public Properties

    public bool ReadyToShutdown
    {
        get
        {
            lock (lockObject)
            {
                return this.shuttingDown && this.readyToShutdown;
            }
        }
        private set
        {
            this.readyToShutdown = value;
            if (this.readyToShutdown)
            {
                //let interested parties know that the queue is now empty 
                //and ready to shutdown
                IsReadyToShutdown.Raise(this);
            }
        }
    }

    /// <summary>
    /// Gets or sets a value indicating whether or not the queue should shut down 
    /// when it is finished with the last action it has enqueued to process.
    /// If the queues owner is shutting down, it needs to notify the queue,
    /// and wait for a QueueIsEmpty event to be fired, at which point the reset 
    /// event will exit ... the owner shouldn't actually destroy the queue 
    /// until all actions have been performed.
    /// </summary>
    public bool ShuttingDown
    {
        get
        {
            lock (lockObject)
            {
                return this.shuttingDown;
            }
        }
        set
        {
            lock (lockObject)
            {
                bool startThread = false;
                if (value == false)
                {
                    readyToShutdown = false;
                    //if we were shutting down...but, now are not
                    startThread = this.shuttingDown;
                }

                this.shuttingDown = value;

                //if we were shutting down, but now are not...
                //we need to restart the processing actions thread
                if (startThread)
                {
                    StartThread();
                }
            }

            this.resetEvent.Set();
        }
    }

    #endregion Public Properties

    #region Private Methods

    private void StartThread()
    {
        var processActionsThread = new Thread(this.ProcessActions);
        processActionsThread.Name = queuName;
        processActionsThread.IsBackground = true;
        processActionsThread.Start();            
    }

    /// <summary>
    /// Processes the actions in a while loop, resetting a ManualResetEvent that 
    /// is triggered in the EnqueueAction method and ShuttingDown property.
    /// </summary>
    private void ProcessActions()
    {
        while (true)
        {
            Action action = null;
            lock (lockObject)
            {
                //if there are any actions, then get the first one out of the queue
                if (queueOfActions.Count > 0)
                {
                    action = queueOfActions.Dequeue();
                }
            }
            if (action != null)
            {
                action();
            }
            lock (lockObject)
            {
                //if any actions were added since the last one was processed, go 
                //back around the loop and do the next one
                if (this.queueOfActions.Count > 0)
                {
                    continue;
                }

                if (this.shuttingDown)
                {
                    //ReadyToShutdown setter will raise IsReadyToShutdown
                    ReadyToShutdown = true;
                    //get out of the method if the user has chosen to shutdown, 
                    //and there are no more actions to process
                    return;
                }                    
                this.resetEvent.Reset();
            }

            this.resetEvent.WaitOne();
        }
    }

    #endregion Private Methods
}

【问题讨论】:

  • 您是否有特定的性能问题,或者这只是“这里有一段代码,请查看”请求? codereview.stackexchange.com 网站最适合。
  • 不得不同意 Hans 的观点,似乎需要代码审查。
  • 好吧,我不会在 Stack Overflow 上问很多问题,而且在发布此内容之前我已经查看了常见问题解答......其中规定了一项要求:“如果您的问题通常涵盖。 ..一种软件算法”就是这样。而且,我对 .net 对象的功能有一些具体问题:ManualResetEvent。
  • @HansPassant 在您建议在 codereview.stackexchange.com 上发布此内容后,我发布了一个完全不同的问题:stackoverflow.com/questions/9344028/… 只是被建议添加代码示例。拜托,现在有人向我开枪!!!

标签: c# multithreading locking thread-safety manualresetevent


【解决方案1】:

在调用 ManualResetEvent.WaitOne() 时被阻塞的线程会从 CPU 中取出,并且不会再次被操作系统考虑进行调度,直到应该唤醒它们的事件(即调用 Set())发生。因此,在等待发出信号时,它们处于非活动状态,不会消耗 CPU 周期。

【讨论】:

    【解决方案2】:

    除了为容纳线程的实际堆栈而保留的内存以及运行时或内核保留的有关它们的任何记帐信息之外,等待线程的存在应该不会对性能产生持续影响。所以不,真正等待的线程除了消耗 RAM 不会做任何事情。

    也就是说,我不确定你为什么要编写这段代码,因为 .Net 有一个内置的线程池,以及你可能更喜欢自己的并发工具。

    【讨论】:

    • 我意识到 .Net 有一个内置的 ThreadPool,但是,如果池中没有可用的线程,则创建一个线程需要半秒钟。线程的消费者不能冒险等待。您具体指的是哪些并发工具?我需要单独的线程来按顺序执行操作,并且我需要知道这些操作何时完成。如果您有具体建议,我肯定更喜欢使用预先构建的东西。
    • 我建议查看任务并行库。您的操作由 Task 实例表示,它们可以串在一起以表示 Task 之间的依赖关系。因此,当完成时,它的依赖项将运行。
    • 我将任务并行库用于其他事情。由于上面评论中提到的事情,以及不能保证继续在同一个线程上的事实,它不会为此工作。 (如果我错了,请纠正我)。
    • 好吧,我不想为此争论不休。如果你知道你的实现是需要的,那很好。然而,TPL 确实允许您定义自定义任务工厂、自定义调度程序等。它允许您在定义的接口中实现您的细节(线程等)。我发现它的价值,所以我尝试使用它。
    • 感谢您的帮助。我将更多地研究 TPL 的其他方面。
    猜你喜欢
    • 2010-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多