异步处理消息时的BasicAck

时间:2016-04-22 15:43:18

标签: c# rabbitmq amqp

我正在尝试设置RabbitMQ消息传递队列,以便我可以发送消息以启动长时间运行的进程,并且还可以发送消息以在需要时取消长时间运行的进程。所以我开始使用EventingBasicConsumer并在我的Recieved处理程序中执行类似的操作:

if (startProcess) 
{
    // start a long running process
}
else if (cancelProcess)
{
    // cancel the currently running process
}
channel.BasicAck(ea.DeliveryTag, false);

这不起作用,因为EventingBasicConsumer不是多线程的,并且一次只能处理一条消息。因此,在完成长时间运行的过程之前,它无法处理取消消息(此时,显然没有任何意义)。接下来我尝试了这个:

if (startProcess) 
{
    Task.Run(() => {
        // start a long running process
    }
}
else if (cancelProcess)
{
    // cancel the currently running process
}
channel.BasicAck(ea.DeliveryTag, false);

这很有效。我现在可以取消长时间运行的进程...但是,我正在确认请求立即运行长时间运行的进程,而不是在完成之后。这意味着如果长时间运行的进程崩溃,则消息已被删除。因此,这将需要原始发送者跟踪并让接收者必须发回消息以说明它已经完成并且这一切都变得有点复杂。

所以我想也许我可以将EventingBasicConsumer更改为始终在新线程上触发其Received事件。所以我创建了这样的东西:

public class AsyncRabbitConsumer : DefaultBasicConsumer
{
    // code all the same as EventingBasicConsumer except this bit:
    public override void HandleBasicDeliver(string consumerTag,
        ulong deliveryTag,
        bool redelivered,
        string exchange,
        string routingKey,
        IBasicProperties properties,
        byte[] body)
    {
        base.HandleBasicDeliver(consumerTag,
            deliveryTag,
            redelivered,
            exchange,
            routingKey,
            properties,
            body);
        if (Received != null)
        {
            var args = new BasicDeliverEventArgs(consumerTag,
                    deliveryTag,
                    redelivered,
                    exchange,
                    routingKey,
                    properties,
                    body);

            Task.Run(() =>
            {
                Received(this, args);
            });
        }
    }
}

现在在我的第一段代码中,我可以让它处理取消消息,而长时间运行的进程仍在运行长时间运行的进程不会Ack并删除它的消息,直到它实际上完成(或取消)。所以这应该是伟大的...除非我取消我得到这个:

  

RabbitMQ.Client.dll中发生了'RabbitMQ.Client.Exceptions.AlreadyClosedException'类型的异常,但未在用户代码中处理

     

附加信息:已经关闭:AMQP操作被中断:AMQP关闭原因,由Peer发起,代码= 406,text =“PRECONDITION_FAILED - 未知交付标签3”,classId = 60,methodId = 80,cause = < / p>

从似乎是启动长时间运行过程的线程的channel.BasicAck步骤开始。那么这里发生了什么?我认为确认(首先是取消消息,然后是长时间运行的流程消息)在这里越过了。是否有任何体面的方法来理顺这一点?还是我在错误的树上吠叫?

值得注意的是,取消长时间运行的过程并不是即时的。它将在下一个方便点取消,因此几乎可以肯定取消消息将在长时间运行过程结束之前完成处理。

2 个答案:

答案 0 :(得分:0)

你能做的就是拥有像消费者一样的东西 - 第一个是长期运行的过程,第二个是杀死长时间运行过程的代理。第一个将接收消息,处理它并在完成处理时发出ACK,如果检测到终止信号也会发出ACK。该对中的代理显然会收到取消消息并终止第一个,并产生第一个的另一个实例。显然,这要求流程(消费者)在RMQ之外进行通信。

我想到的另一件事(但我从未尝试过这样的事情)是你在消费者中将预取计数设置为2 ,同时&#34;处理单个数据消息& #34;,将第二条消息发布到代理(转发),除非它是CANCEL消息,在这种情况下你确认它们 - CANCEL和DATA(就像那样调用它)消息中止了处理。

另一种选择可能是在长期运行的过程中&#34;你有两个消费者线程,每个消费者线程使用自己的频道。

答案 1 :(得分:0)

我遇到了同样的错误,因为在 BasicConsume 方法中, autoAck 标志为true。现在,我已将标志更改为false,并且长时间运行的过程在BasicAck方法中没有出现错误。

channel.BasicConsume(queue: "test", autoAck: false, consumer: consumer);