我正在使用C#(.NET 4.5,VS2012)编写的Windows服务,该服务使用RabbitMQ(通过订阅接收消息)。有一个派生自DefaultBasicConsumer
的类,在这个类中有两个实际的消费者(因此有两个渠道)。因为有两个通道,两个线程处理传入的消息(来自两个不同的队列/路由键),并且都调用相同的HandleBasicDeliver(...)
函数。
现在,当调用windows服务OnStop()
时(当有人停止服务时),我想让这两个线程完成处理他们的消息(如果他们当前正在处理消息),发送ack到服务器,然后停止服务(中止线程等)。
我想到了多种解决方案,但它们似乎都不是很好。这是我试过的:
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的连接(以确保没有新消息到达),然后等待几秒钟(如果有任何消息正在处理,则完成处理)关闭申请。问题是,在关闭应用程序之前我应该等待的确切秒数是未知的,所以这不是一个优雅或精确的解决方案。我意识到设计不符合单一责任原则,这可能导致缺乏解决方案。但是,如果不重新设计项目,是否可以很好地解决这个问题?
答案 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();
}