c# - Monitor.PulseAll 的正确用法?

标签 c# .net multithreading

我正在为我们正在运行的 ASP.NET 应用程序编写我们自己的记录器。在当前的代码库中,我们似乎遇到了线程问题,我正在尝试逐个组件地消除以找出导致此问题的原因。症状是应用程序启动后网络服务器上的 CPU 达到 100%。

我要删除的第一件事是我编写的记录器,因为它是代码库中最新添加的内容之一。记录器背后的想法如下:

  • 1 个名为 LogManager 的静态类,用于跟踪所有 ILogger 实现
  • 1 个名为 LogSettings 的静态类,用于跟踪配置文件中的所有设置。
  • 1 个具有 MessageQueue 的 XmlLogger,用于保存消息并将它们写入 xml 文件。

可以从任何地方调用静态类,并且 XmlLogger 有一个后台线程从 MessageQueue 获取消息并处理它。所以我需要使对该队列的访问是线程安全的。 MessageQueue 类的代码如下所示:

public sealed class MessageQueue
    {
        #region Private Fields

        private readonly Queue<IMessage> _messageQueue;

        #endregion

        #region Constructor

        /// <summary>
        /// <para>Creates a new instance of the class and initializes all internal fields.</para>
        /// </summary>
        public MessageQueue()
        {
            _messageQueue = new Queue<IMessage>();
        }

        #endregion

        #region Properties

        /// <summary>
        /// <para>Gets the number of <see cref="IMessage"/> objects in the queue.</para>
        /// </summary>
        public int NumberOfMessage
        {
            get
            {
                lock(_messageQueue)
                {
                    return _messageQueue.Count;
                }
            }
        }

        #endregion

        #region Public Members

        /// <summary>
        /// <para>Adds a new <see cref="IMessage"/> to the bottom of the queue.</para>
        /// </summary>
        /// <param name="message">The <see cref="IMessage"/> to be added to the queue.</param>
        public void AddMessage(IMessage message)
        {
            lock(_messageQueue)
            {
                _messageQueue.Enqueue(message);
                Monitor.PulseAll(_messageQueue);
            }
        }

        /// <summary>
        /// <para>Gets the first <see cref="IMessage"/> from the queue.</para>
        /// </summary>
        /// <returns>The first <see cref="IMessage"/> from the queue.</returns>
        public IMessage GetMessage()
        {
            lock(_messageQueue)
            {
                while (_messageQueue.Count == 0) Monitor.Wait(_messageQueue);
                return _messageQueue.Dequeue();
            }
        }

        #endregion
    }

向任何记录器的 MessageQueue 添加消息的代码如下所示:

/// <summary>
        /// <para>Logs a specified <see cref="IMessage"/> in the various loggers.</para>
        /// </summary>
        /// <param name="message">The <see cref="IMessage"/> to be logged.</param>
        public void LogMessage(IMessage message)
        {
            lock(_loggerLock)
            {
                foreach (AbstractLogger logger in _loggers)
                    logger.MessageQueue.AddMessage(message);
            }
        }

我已经锁定了 LogManager,因为消息的插件不应与从队列中添加/删除记录器冲突。我确定这里不存在问题。 下面的代码显示了包含 MessageQueue 对象的 XmlLogger 的处理例程。

private void ProcessMessage()
{
    // This thread should be running indefinitly untill the abort is called.
    while (true)
    {
        try
        {
            // Get the first message available from the Queue
            IMessage message = MessageQueue.GetMessage();

            // If we could retrieve a valid message, process it.
            if (message != null && (LogSettings.Level & message.Level) == message.Level)
            {
                // Determine the path to the logfile
                string logpath = DetermineFilePath(message.Context);

                // Write the message into the file.
                WriteMessage(logpath, message);
            }
        }
        catch(ThreadAbortException)
        {
            break;
        }
        catch (Exception ex)
        {
            Debug.WriteLine(string.Format("InnerException caught: '{0}'", ex.Message));
        }
        finally
        {
            Thread.Sleep(100);
        }
    }
}

我想知道的是,这段代码是使用 Monitor.Pulse 和 Monitor.Wait 例程的正确方法吗?如果不是,我应该更改什么以防止出现问题?

编辑:请不要就使用现有记录器提出问题/讨论。这不是一个选择

EDIT2:从 anwser 运行该工具,这是输出:

------------------------------------ 5956 Kernel: 0 User: 156250 TenForce.Execution.Logging.Loggers.XmlLogger.ProcessMessage System.Threading.ExecutionContext.Run System.Threading.ExecutionContext.Run System.Threading.ThreadHelper.ThreadStart Other Stacks:

TenForce.Execution.Logging.Loggers.XmlLogger.ProcessMessage System.Threading.ExecutionContext.Run System.Threading.ExecutionContext.Run System.Threading.ThreadHelper.ThreadStart

对我来说听起来有点太高了......

最佳答案

是的,看起来不错;您的出队正确地进行了检查/循环(并且没有假设脉冲意味着现在有数据,这可能会导致异常),所以大部分时间应该是空闲的。你的入队是相似的。也许我会补充:如果您添加了第一项,则仅脉冲:

        lock(_messageQueue)
        {
            _messageQueue.Enqueue(message);
            if(_messageQueue.Count == 1) Monitor.PulseAll(_messageQueue);
        }

(否则,您的读者不会等待)

要调查高 CPU,请使用 CPU 分析器 - 猜测通常不是一个好的方法。山姆藏红花 has one here我们使用的;它可以在生产网络服务器上使用(只是......不是所有的时间!)

关于c# - Monitor.PulseAll 的正确用法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8307318/

相关文章:

c# - 构建可扩展数据模型,EF 4

.net - 在 SpecFlow 生成的文件中 : Runtime Version: 4. 0.30319.468 与 4.0.30319.239 - 它来自哪里?

java - 如何监听当前线程的变化?

C# Regex 匹配不包含特定字符串的字符串?

c# - 在 Silverlight 中调用 Dispatcher.CheckAccess() 是好的形式吗?

c# - 在运行时更改 WPF DataGrid 整个列的背景颜色

c# - Path.Combine 是否曾在 .net 中进行过网络调用?

c# - 将 DataGridView CheckBox Column 用于字符串数据列

java - 自定义类队列数据结构的并发帮助

python - 同时训练两个模型