简体   繁体   English

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

[英]Wait for RabbitMQ Threads to finish in Windows Service OnStop()

I am working on a windows service written in C# (.NET 4.5, VS2012), which uses RabbitMQ (receiving messages by subscription). 我正在使用C#(.NET 4.5,VS2012)编写的Windows服务,该服务使用RabbitMQ(通过订阅接收消息)。 There is a class which derives from DefaultBasicConsumer , and in this class are two actual consumers (so two channels). 有一个从DefaultBasicConsumer派生的类,该类中有两个实际使用者(因此有两个通道)。 Because there are two channels, two threads handle incoming messages (from two different queues/routing keys) and both call the same HandleBasicDeliver(...) function. 因为有两个通道,所以两个线程处理传入消息(来自两个不同的队列/路由键),并且都调用相同的HandleBasicDeliver(...)函数。

Now, when the windows service OnStop() is called (when someone is stopping the service), I want to let both those threads finish handling their messages (if they are currently processing a message), sending the ack to the server, and then stop the service (abort the threads and so on). 现在,当调用Windows服务OnStop()时(当某人停止该服务时),我要让这两个线程都完成对消息的处理(如果他们当前正在处理消息),将ack发送到服务器,然后停止服务(中止线程等)。

I have thought of multiple solutions, but none of them seem to be really good. 我考虑过多种解决方案,但是似乎都不是一个好方案。 Here's what I tried: 这是我尝试过的:

  • using one mutex; 使用一个互斥锁; each thread tries to take it when entering HandleBasicDeliver, then releases it afterwards. 每个线程在进入HandleBasicDeliver时都会尝试使用它,然后再释放它。 When OnStop() is called, the main thread tries to grab the same mutex, effectively preventing the RabbitMQ threads to actually process any more messages. 调用OnStop() ,主线程尝试获取相同的互斥量,从而有效地阻止RabbitMQ线程实际处理更多消息。 The disadvantage is, only one consumer thread can process a message at a time. 缺点是一次只能有一个使用者线程可以处理一条消息。
  • using two mutexes: each RabbitMQ thread has uses a different mutex, so they won't block each other in the HandleBasicDeliver() - I can differentiate which thread is actually handling the current message based on the routing key. 使用两个互斥锁:每个RabbitMQ线程都有一个不同的互斥锁,因此它们在HandleBasicDeliver()中不会互相阻塞-我可以根据路由键区分哪个线程实际上正在处理当前消息。 Something like: 就像是:

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

    When OnStop() is called, the main thread will try to grab both mutexes; 调用OnStop() ,主线程将尝试获取两个互斥锁; once both mutexes are "in the hands" of the main thread, it can proceed with stopping the service. 一旦两个互斥锁都在主线程“手中”,它就可以继续停止服务。 The problem: if another consumer would be added to this class, I'd need to change a lot of code. 问题是:如果将另一个使用者添加到此类中,则需要更改很多代码。

  • using a counter, or CountdownEvent . 使用计数器或CountdownEvent Counter starts off at 0, and each time HandleBasicDeliver() is entered, counter is safely incremented using the Interlocked class. 计数器从0开始计数,每次输入HandleBasicDeliver() ,都可以使用Interlocked类安全地递增计数器。 After the message is processed, counter is decremented. 处理完消息后,计数器递减。 When OnStop() is called, the main thread checks if the counter is 0. Should this condition be fulfilled, it will continue. 调用OnStop() ,主线程将检查计数器是否为0。如果满足此条件,它将继续。 However, after it checks if counter is 0, some RabbitMQ thread might begin to process a message. 但是,在检查计数器是否为0之后,某些RabbitMQ线程可能开始处理消息。
  • When OnStop() is called, closing the connection to the RabbitMQ (to make sure no new messages will arrive), and then waiting a few seconds ( in case there are any messages being processed, to finish processing) before closing the application. 调用OnStop() ,关闭与RabbitMQ的连接(以确保没有新消息到达),然后等待几秒钟(以防万一正在处理任何消息,以完成处理),然后关闭应用程序。 The problem is, the exact number of seconds I should wait before shutting down the apllication is unknown, so this isn't an elegant or exact solution. 问题是,我不知道在关闭连接之前应该等待的确切秒数,因此这不是一个优雅的解决方案。

I realize the design does not conform to the Single Responsibility Principle, and that may contribute to the lack of solutions. 我意识到设计不符合“单一责任原则”,这可能会导致缺乏解决方案。 However, could there be a good solution to this problem without having to redesign the project? 但是,是否有一个很好的解决方案,而不必重新设计项目?

We do this in our application, The main idea is to use a CancellationTokenSource 我们在应用程序中执行此操作,主要思想是使用CancellationTokenSource

On your windows service add this: 在Windows服务上添加以下内容:

private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();

Then in your rabbit consumers do this: 1. change from using Dequeue to DequeueNoWait 2. have your rabbit consumer check the cancellation token 然后在您的兔子消费者中执行以下操作:1.从使用Dequeue更改为DequeueNoWait 2.让您的兔子消费者检查取消令牌

Here is our code: 这是我们的代码:

        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!
                  }
}

Usually, how we ensure a windows service not stop before processing completes is to use some code like below. 通常,我们如何确保Windows服务在处理完成之前不会停止,是使用以下代码。 Hope that help. 希望对您有所帮助。

    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();
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM