多个线程之间的队列问题

时间:2018-03-08 12:22:44

标签: c# .net queue

关于在多线程应用程序中正确使用.NET Queue的主题有很多问题和文章,但是我找不到具体问题的主题。

我们有一个Windows服务,它通过一个线程将消息接收到队列中,然后在另一个线程中出列并处理。

我们在排队和出列时使用lock,并且服务运行良好了大约2年没有任何问题。有一天,我们注意到已经记录了数千条消息(因此已排队),但从未出列/处理过,它们似乎已经以某种方式被跳过,这对于队列来说是不可能的。

我们无法复制导致它的情况,因为我们并不知道究竟是什么导致了它,因为我们知道这一天与其他任何一天都没有什么不同。

我们唯一的想法是处理队列的并发性。我们没有使用我们计划使用的ConcurrentQueue数据类型,希望它是一种补救措施。

查看Queue类型的来源的一个想法是它在内部使用数组,一旦这些缓冲区达到一定长度就必须调整大小。我们假设在完成此操作时,一些消息丢失了。

我们的开发经理的另一个想法是,在多核处理器设置上使用多个线程意味着即使使用了锁,各个核也在处理其本地寄存器中的数据,这可能导致它们处理不同的数据。他说他们不会在相同的内存上工作,似乎认为lock只能按预期使用多个线程的单个核心处理器。

阅读有关ConcurrentQueue使用volatile的更多信息我不确定这会有所帮助,因为我已经读过使用锁提供了使用最新内存状态的线程的更强保证。

我对这个特定的主题知之甚少,所以我的问题是经理的想法是否合理,以及我们是否可能错过了正确使用队列所需的东西。

代码片段供参考(原谅凌乱的代码,它确实需要重构):

public sealed class Message
{

    public void QueueMessage(long messageId, Message msg)
    {
        lock (_queueLock)
        {
            _queue.Enqueue(new QueuedMessage() { Id = messageId, Message = msg });
        }
    }

    public static void QueueMessage(string queueProcessorName, long messageId, Message msg)
    {
        lock (_messageProcessors[queueProcessorName]._queueLock)
        {
            _messageProcessors[queueProcessorName].QueueMessage(messageId, msg);
            _messageProcessors[queueProcessorName].WakeUp(); // Ensure the thread is awake
        }
    }

    public void WakeUp()
    {
        lock(_monitor)
        {
            Monitor.Pulse(_monitor);
        }
    }

    public void Process()
    {
        while (!_stop)
        {
            QueuedMessage currentMessage = null;

            try
            {
                lock (_queueLock)
                {
                    currentMessage = _queue.Dequeue();
                }
            }
            catch(InvalidOperationException i)
            {
                // Nothing in the queue
            }

            while(currentMessage != null)
            {
                IContext context = new Context();
                DAL.Message msg = null;

                try
                {
                    msg = context.Messages.SingleOrDefault(x => x.Id == currentMessage.Id);
                }
                catch (Exception e)
                {
                    // TODO: Handle these exceptions better. Possible infinite loop.

                    continue; // Keep retrying until it works
                }

                if (msg == null) { 
                    // TODO: Log missing message
                    continue;
                }

                try
                {
                    msg.Status = DAL.Message.ProcessingState.Processing;
                    context.Commit();
                }
                catch (Exception e)
                {
                    // TODO: Handle these exceptions better. Possible infinite loop.

                    continue; // Keep retrying until it works
                }

                bool result = false;
                try {
                    Transformation.TransformManager mgr = Transformation.TransformManager.Instance();
                    Transformation.ITransform transform = mgr.GetTransform(currentMessage.Message.Type.Name, currentMessage.Message.Get("EVN:EventReasonCode"));

                    if (transform != null){
                        msg.BeginProcessing = DateTime.Now;
                        result = transform.Transform(currentMessage.Message);

                        msg.EndProcessing = DateTime.Now;
                        msg.Status = DAL.Message.ProcessingState.Complete;
                    }
                    else {
                        msg.Status = DAL.Message.ProcessingState.Failed;
                    }

                    context.Commit();
                }
                catch (Exception e)
                {
                    try
                    {
                        context = new Context();
                        // TODO: Handle these exceptions better
                        Error err = context.Errors.Add(context.Errors.Create());

                        err.MessageId = currentMessage.Id;
                        if (currentMessage.Message != null)
                        {
                            err.EventReasonCode = currentMessage.Message.Get("EVN:EventReasonCode");
                            err.MessageType = currentMessage.Message.Type.Name;
                        }
                        else {
                            err.EventReasonCode = "Unknown";
                            err.MessageType = "Unknown";
                        }

                        StringBuilder sb = new StringBuilder("Exception occured\n");
                        int level = 0;
                        while (e != null && level < 10)
                        {
                            sb.Append("Message: ");
                            sb.Append(e.Message);
                            sb.Append("\nStack Trace: ");
                            sb.Append(e.StackTrace);
                            sb.Append("\n");

                            e = e.InnerException;
                            level++;
                        }

                        err.Text = sb.ToString();
                    }
                    catch (Exception ne) { 

                        StringBuilder sb = new StringBuilder("Exception occured\n");
                        int level = 0;
                        while (ne != null && level < 10)
                        {
                            sb.Append("Message: ");
                            sb.Append(ne.Message);
                            sb.Append("\nStack Trace: ");
                            sb.Append(ne.StackTrace);
                            sb.Append("\n");

                            ne = ne.InnerException;
                            level++;
                        }

                        EventLog.WriteEntry("Service", sb.ToString(), EventLogEntryType.Error);
                    }
                }

                try
                {
                    context.Commit();

                    lock (_queueLock)
                    {
                        currentMessage = _queue.Dequeue();
                    }
                }
                catch (InvalidOperationException e)
                { 
                    currentMessage = null;    // No more messages in the queue 
                }
                catch (Exception ne)
                {

                    StringBuilder sb = new StringBuilder("Exception occured\n");
                    int level = 0;
                    while (ne != null && level < 10)
                    {
                        sb.Append("Message: ");
                        sb.Append(ne.Message);
                        sb.Append("\nStack Trace: ");
                        sb.Append(ne.StackTrace);
                        sb.Append("\n");

                        ne = ne.InnerException;
                        level++;
                    }

                    EventLog.WriteEntry("Service", sb.ToString(), EventLogEntryType.Error);
                }
            }

            lock (_monitor)
            {
                if (_stop) break;

                Monitor.Wait(_monitor, TimeSpan.FromMinutes(_pollingInterval));

                if (_stop) break;
            }
        }
    }

    private object _monitor = new object();
    private int _pollingInterval = 10;
    private volatile bool _stop = false;
    private object _queueLock = new object();
    private Queue<QueuedMessage> _queue = new Queue<QueuedMessage>();
    private static IDictionary<string, Message> _messageProcessors = new Dictionary<string, Message>();
}

2 个答案:

答案 0 :(得分:1)

  

所以我的问题是经理的想法听起来是否合理

嗯。没有。如果所有这些同步措施只适用于单核机器,世界将在几十年前的完整混乱中结束。

  

以及我们是否可能遗漏了正确使用队列所需的内容。

就你的描述而言,你应该没事。我会看看你如何找到你有这个问题。如果我只是关闭服务或重新启动机器,那么日志会进入然后在没有正常出列的情况下消失,这不是默认情况吗?当你的应用程序实际上正在运行时,你确定丢失了它们吗?

答案 1 :(得分:1)

您将用于锁定的对象声明为私有对象。

如果您尝试这样做:

class Program
    {
        static void Main(string[] args)
        {
            Test test1 = new Test();
            Task Scan1 = Task.Run(() => test1.Run("1"));


            Test test2 = new Test();
            Task Scan2 = Task.Run(() => test2.Run("2"));

            while(!Scan1.IsCompleted || !Scan2.IsCompleted)
            {
                Thread.Sleep(1000);
            }

        }
    }

    public class Test
    {
        private object _queueLock = new object();
        public async Task Run(string val)
        {
            lock (_queueLock)
            {
                Console.WriteLine($"{val} locked");
                Thread.Sleep(10000);
                Console.WriteLine($"{val} unlocked");
            }
        }
    }

您会注意到即使另一个线程在内部运行,也会执行锁定下的代码。

但是如果你改变了

private object _queueLock = new object();

private static object _queueLock = new object();

它会改变锁定的工作方式。

现在,这是你的问题取决于你是否有多个实例,或者所有类都在运行同一个类。