我有一个包含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()
它可以正常工作。谁能告诉我我做错了什么?
答案 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
添加到activemq.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
并且
答案 1 :(得分:1)
附注:您不会在Main
方法中等待您的任务,您可以使用Task.WaitAll
方法执行此操作,这将阻止主线程,直到所有消息都已传递。另外,avoid the usage the StartNew
method,一般情况下使用Task.Run
会更好。
现在,回到消息。根据{{3}}:
消费者的并发注意事项
在当前实现中,每个
IConnection
实例都由单个后台线程支持,后者从套接字读取并将结果事件分派给应用程序。从版本
3.5.0
开始,应用程序回调处理程序可以调用阻止操作(例如IModel.QueueDeclare
或IModel.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);
在这种情况下,您将以基于事件的方式获取消息,而不会阻止频道。
可以找到其他示例EventingBasicConsumer
或here。可以在here中找到.Net中消息传递的绝佳参考。