并行消耗来自一个队列的消息

时间:2017-04-10 19:36:07

标签: c# .net task-parallel-library activemq

我有一个包含N条消息的队列。现在我想创建一个控制台应用程序(使用者),它创建给定数量的任务/线程,每个线程将从队列中获取1条消息,处理它并接收新消息......直到队列为空。

到目前为止,这就是我所拥有的:

private static void Main(string[] args)
{
    var runner = new Runner();
    for (var j = 0; j < 5; j++)
    {
        var task = Task.Factory.StartNew(() => { runner.ReceiveMessageFromServer(); });
    }
}

public class QRegistrator : IDisposable
{
    private const string activeMqUri = "activemq:tcp://localhost:61616";

    private readonly IConnection _connection;

    private readonly ISession _session;

    public QRegistrator(/*string activeMqUri*/)
    {
        var connectionFactory = new ConnectionFactory(activeMqUri);
        _connection = connectionFactory.CreateConnection();
        _connection.Start();
        _session = _connection.CreateSession();
    }

    public void Dispose()
    {
        _connection?.Close();
    }

    public IMessageConsumer CreateConsumer(string queueName, string topicName)
    {
        //there have to be new session for each concurrent consumer
        var session = _connection.CreateSession();
        var queue = new ActiveMQQueue(queueName);
        var consumer = string.IsNullOrWhiteSpace(topicName) ? session.CreateConsumer(queue) : session.CreateConsumer(queue, $"topic = '{topicName}'");
        return consumer;
    }
}

和课程Runner

public void ReceiveMessageFromServer()
{
    var consumer = _registrator.CreateConsumer("myQueue", null);

    while (true)
    {
        ITextMessage message = consumer.Receive() as ITextMessage;
        if (message == null)
        {
            break;
        }
        Console.WriteLine("Received message with ID:   " + message.NMSMessageId);
        DoSomething();
    }
}

但是,当我运行此代码时,有时它不会创建连接,有时它不会创建会话等。我真的不明白。当我尝试没有for循环(但仍然是一个task())时,它的行为相同。当我尝试没有任务时,只调用runner.ReceiveMessageFromServer()它可以正常工作。谁能告诉我我做错了什么?

2 个答案:

答案 0 :(得分:2)

你的问题是prefetchPolicy。

persistent queues (default value: 1000)
non-persistent queues (default value: 1000)
persistent topics (default value: 100)
non-persistent topics (default value: Short.MAX_VALUE - 1)

所有消息都被分派到第一个连接的使用者,当另一个消息连接时他没有收到消息,因此如果您有队列的并发使用者,则需要将prefetchPolicy设置为低于默认值的值,以更改此行为。例如,将此jms.prefetchPolicy.queuePrefetch=1添加到a​​ctivemq.xml中的uri配置中,或者将其设置在客户端URL上,如下所示

我不知道C#是否有可能,但试试这个

private const string activeMqUri = "activemq:tcp://localhost:61616?jms.prefetchPolicy.queuePrefetch=1";

如果它不起作用,你可以通过将它添加到activemq.xml文件在代理端设置它:

<destinationPolicy>
    <policyMap>
      <policyEntries>
        <!--  http://activemq.apache.org/per-destination-policies.html -->
        <policyEntry queue=">" queuePrefetch="1" > 
        </policyEntry>
      </policyEntries>
    </policyMap>
</destinationPolicy>
  

建议使用较大的预取值以获得较高的高性能   消息卷。但是,对于较低的消息量,每个消息量   消息需要很长时间才能处理,预取应该设置为1。   这可确保消费者一次只处理一条消息。   但是,将预取限制指定为零将导致使用者   一次一个地轮询消息,而不是消息   推向消费者。

查看http://activemq.apache.org/what-is-the-prefetch-limit-for.html

并且

http://activemq.apache.org/destination-options.html

答案 1 :(得分:1)

附注:您不会在Main方法中等待您的任务,您可以使用Task.WaitAll方法执行此操作,这将阻止主线程,直到所有消息都已传递。另外,avoid the usage the StartNew method,一般情况下使用Task.Run会更好。

现在,回到消息。根据{{​​3}}:

  

消费者的并发注意事项

     

在当前实现中,每个IConnection实例都由单个后台线程支持,后者从套接字读取并将结果事件分派给应用程序。

     

从版本3.5.0开始,应用程序回调处理程序可以调用阻止操作(例如IModel.QueueDeclareIModel.BasicCancel)。 IBasicConsumer回调被调用并发

所以,根据current docs,您需要为您的消费者使用其他类:this question and answers there,如下所示:

var channel = _connection.CreateModel();
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (ch, ea) =>
    {
        var body = ea.Body;
        // ... process the message
        channel.BasicAck(ea.DeliveryTag, false);
    };
String consumerTag = channel.BasicConsume(queueName, false, consumer);

在这种情况下,您将以基于事件的方式获取消息,而不会阻止频道。

可以找到其他示例EventingBasicConsumerhere。可以在here中找到.Net中消息传递的绝佳参考。