这个后台线程队列是一个高性能的实现吗?

时间:2012-02-18 17:22:39

标签: c# multithreading locking thread-safety manualresetevent

具体来说,我想知道:

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

如果我可以选择使用多个执行较少工作的BackgroundThreadQueues,或者执行更多工作的一个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
}

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

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

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