Является ли эта очередь фоновых потоков производительной реализацией? - PullRequest
0 голосов
/ 18 февраля 2012

В частности, мне интересно:

Будет ли ManualResetEvent потреблять ресурсы, пока он находится в состоянии ожидания?Применимо ли снижение производительности переключения контекста к потокам, которые находятся в состоянии ожидания?

Если у меня есть выбор, использовать несколько BackgroundThreadQueues, которые работают меньше по одному, или один BackgroundThreadQueue, который выполняет больше работы, и я выбираюиспользовать несколько ... Будут ли очереди ожидающих потоков влиять на производительность процесса, пока они ничего не делают?

Есть ли лучшая очередь потоков FIFO, которую я должен использовать в C #, или другая стратегия блокировки?

Любые предложения приветствуются.

/// <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 ]

1 голос
/ 19 февраля 2012

Потоки, заблокированные при вызове ManualResetEvent.WaitOne(), снимаются с ЦП и не рассматриваются ОС для планирования до тех пор, пока не произойдет событие, которое должно их разбудить (т.е. вызов Set()).Следовательно, в ожидании сигнала они неактивны и не потребляют циклы ЦП.

0 голосов
/ 18 февраля 2012

Простое существование ожидающих потоков не должно оказывать никакого постоянного влияния на производительность, кроме памяти, зарезервированной для размещения фактического стека потока, и любой учетной информации, которую хранит среда выполнения или ядро ​​относительно них. Так что нет, действительно ожидающие потоки не будут ничего делать, кроме как потреблять оперативную память.

Тем не менее, я не уверен, почему вы пишете этот код, поскольку .Net имеет встроенный пул потоков и инструменты параллелизма, которые вы, вероятно, предпочтете своим собственным.

...