等待RabbitMQ线程在Windows Service OnStop()中完成

时间:2016-02-10 12:47:06

标签: c# multithreading windows-services rabbitmq

我正在使用C#(.NET 4.5,VS2012)编写的Windows服务,该服务使用RabbitMQ(通过订阅接收消息)。有一个派生自DefaultBasicConsumer的类,在这个类中有两个实际的消费者(因此有两个渠道)。因为有两个通道,两个线程处理传入的消息(来自两个不同的队列/路由键),并且都调用相同的HandleBasicDeliver(...)函数。

现在,当调用windows服务OnStop()时(当有人停止服务时),我想让这两个线程完成处理他们的消息(如果他们当前正在处理消息),发送ack到服务器,然后停止服务(中止线程等)。

我想到了多种解决方案,但它们似乎都不是很好。这是我试过的:

  • 使用一个互斥量;每个线程在输入HandleBasicDeliver时尝试接受它,然后在之后释放它。调用OnStop()时,主线程会尝试获取相同的互斥锁,从而有效地阻止RabbitMQ线程实际处理更多消息。缺点是,一次只有一个消费者线程可以处理消息。
  • 使用两个互斥锁:每个RabbitMQ线程使用不同的互斥锁,因此它们不会在HandleBasicDeliver()中相互阻塞 - 我可以区分哪个 线程实际上是根据路由键处理当前消息。类似的东西:

    HandleBasicDeliver(...)
    {
        if(routingKey == firstConsumerRoutingKey)
        {
            // Try to grab the mutex of the first consumer
        }
        else
        {
            // Try to grab the mutex of the second consumer
        }
    }
    

    调用OnStop()时,主线程将尝试抓取两个互斥锁;一旦两个互斥锁都在主线程的“掌控”中,它就可以继续停止服务。问题:如果另外一个消费者被添加到这个类,我需要更改很多代码。

  • 使用计数器或CountdownEvent。计数器从0开始,每次输入HandleBasicDeliver()时,使用Interlocked类安全地递增计数器。处理完消息后,计数器会递减。当调用OnStop()时,主线程检查计数器是否为0.如果满足此条件,它将继续。但是,在检查counter是否为0之后,某些RabbitMQ线程可能会开始处理消息。
  • 调用OnStop()时,关闭与RabbitMQ的连接(以确保没有新消息到达),然后等待几秒钟(如果有任何消息正在处理,则完成处理)关闭申请。问题是,在关闭应用程序之前我应该​​等待的确切秒数是未知的,所以这不是一个优雅或精确的解决方案。

我意识到设计不符合单一责任原则,这可能导致缺乏解决方案。但是,如果不重新设计项目,是否可以很好地解决这个问题?

2 个答案:

答案 0 :(得分:1)

我们在我们的应用程序中执行此操作,主要思想是使用 CancellationTokenSource

在您的Windows服务上添加以下内容:

private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();

然后在你的兔子消费者这样做: 1.从使用Dequeue更改为DequeueNoWait 2.让您的兔子消费者检查取消令牌

这是我们的代码:

        public async Task StartConsuming(IMessageBusConsumer consumer, MessageBusConsumerName fullConsumerName, CancellationToken cancellationToken)
        {
            var queueName = GetQueueName(consumer.MessageBusConsumerEnum);

            using (var model = _rabbitConnection.CreateModel())
            {
                // Configure the Quality of service for the model. Below is how what each setting means.
                // BasicQos(0="Don't send me a new message until I’ve finished",  _fetchSize = "Send me N messages at a time", false ="Apply to this Model only")
                model.BasicQos(0, consumer.FetchCount.Value, false);
                var queueingConsumer = new QueueingBasicConsumer(model);

                model.BasicConsume(queueName, false, fullConsumerName, queueingConsumer);


                var queueEmpty = new BasicDeliverEventArgs(); //This is what gets returned if nothing in the queue is found.

                while (!cancellationToken.IsCancellationRequested)
                {
                    var deliverEventArgs = queueingConsumer.Queue.DequeueNoWait(queueEmpty);
                    if (deliverEventArgs == queueEmpty)
                    {
                        // This 100ms wait allows the processor to go do other work.
                        // No sense in going back to an empty queue immediately. 
                        // CancellationToken intentionally not used!
                        // ReSharper disable once MethodSupportsCancellation
                        await Task.Delay(100);  
                        continue;
                    }
                    //DO YOUR WORK HERE!
                  }
}

答案 1 :(得分:0)

通常,我们如何确保在处理完成之前不停止Windows服务是使用如下代码。希望有所帮助。

    protected override void OnStart(string[] args)
    {
        // start the worker thread
        _workerThread = new Thread(WorkMethod)
        {
            // !!!set to foreground to block windows service be stopped
            // until thread is exited when all pending tasks complete
            IsBackground = false
        };
        _workerThread.Start();
    }

    protected override void OnStop()
    {
        // notify the worker thread to stop accepting new migration requests
        // and exit when all tasks are completed
        // some code to notify worker thread to stop accepting new tasks internally

        // wait for worker thread to stop
        _workerThread.Join();
    }