I am following this article (http://www.jarloo.com/listening-to-rabbitmq-events/) for consuming the messages on a console application, currently i am concerned about what happens when a user pressing CTRL + C and quits the application.
As a minimum i would prefer it complete processing the current message and ack before exiting the program. I am confused how can i implement this code as i am new to RabbitMQ.
I understand that channel.BasicConsume(queueName, true, consumer); is blocking thread.
Any help will be appreciated.
Here is what I managed to achieve but not sure if it is best way around or there is an improvement. I did not use the cancellationToken as yet not sure if we can use it.
On my console i get the expected result (see screenshot)
public abstract class QueueConnection : IDisposable
{
internal IConnection _connection;
internal IModel _model;
internal IBasicProperties _properties;
internal RabbitMQSettings _settings;
internal protected object _modelLock = new Object();
public QueueConnection(IOptions<RabbitMQSettings> queueConfig)
{
_settings = queueConfig.Value;
}
internal bool CreateModel(string queueName)
{
if (string.IsNullOrEmpty(queueName))
{
throw new ArgumentException("The queue name has to be specified before.");
}
lock (_modelLock)
{
if (!IsConnected) Connect();
if (_model == null || _model.IsClosed)
{
_model = _connection.CreateModel();
// When AutoClose is true, the last channel to close will also cause the connection to close.
// If it is set to true before any channel is created, the connection will close then and there.
_connection.AutoClose = true;
// Configure the Quality of service for the model. Below is how what each setting means.
// BasicQos(0="Dont send me a new message untill I’ve finshed", 1= "Send me one message at a time", false ="Apply to this Model only")
_model.BasicQos(0, 50, false);
const bool durable = true, queueAutoDelete = false, exclusive = false;
_model.QueueDeclare(queueName, durable, exclusive, queueAutoDelete, null);
_properties = RabbitMQProperties.CreateDefaultProperties(_model);
}
}
return true;
}
public void Connect()
{
var connectionFactory = new ConnectionFactory
{
HostName = _settings.HostName,
UserName = _settings.UserName,
Password = _settings.Password,
};
if (_settings.Port.HasValue) connectionFactory.Port = _settings.Port.Value;
if (_settings.Heartbeat.HasValue) connectionFactory.RequestedHeartbeat = _settings.Heartbeat.Value;
if (!string.IsNullOrEmpty(_settings.VirtualHost)) connectionFactory.VirtualHost = _settings.VirtualHost;
_connection = connectionFactory.CreateConnection();
}
public bool IsConnected
{
get { return _connection != null && _connection.IsOpen; }
}
public object GetConnection()
{
return _connection;
}
public void Disconnect()
{
if (_connection != null) _connection.Dispose();
}
void IDisposable.Dispose()
{
Disconnect();
}
}
QueueConsumer Class
public class QueueConsumer : QueueConnection, IQueueConsumer
{
private EventingBasicConsumer consumer;
public QueueConsumer(IOptions<RabbitMQSettings> queueConfig)
:base(queueConfig) {}
public void ReadFromQueue(Action<string, ulong> onDequeue, Action<Exception, ulong> onError)
{
ReadFromQueue(onDequeue, onError, _settings.QueueName);
}
public void ReadFromQueue(Action<string, ulong> onDequeue, Action<Exception, ulong> onError, string queueName)
{
CreateModel(queueName);
consumer = new EventingBasicConsumer(_model);
// Receive the messages
consumer.Received += (o, e) =>
{
try
{
var queueMessage = Encoding.UTF8.GetString(e.Body);
onDequeue.Invoke(queueMessage, e.DeliveryTag);
}
catch (Exception ex)
{
onError.Invoke(ex, e.DeliveryTag);
}
};
// if the consumer shutdown reconnects to rabbitmq and begin reading from the queue again.
consumer.Shutdown += (o, e) =>
{
CreateModel(queueName);
ReadFromQueue(onDequeue, onError, queueName);
};
_model.BasicConsume(queueName, false, consumer);
}
public void AcknowledgeMessage(ulong deliveryTag)
{
if (!IsConnected) Connect();
CreateModel(_settings.QueueName);
_model.BasicAck(deliveryTag, false);
}
public void StopListening()
{
_model.BasicCancel(consumer.ConsumerTag);
}
}
Main Class
static ManualResetEvent _quitEvent = new ManualResetEvent(false);
public static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
Console.WriteLine($"[{DateTime.UtcNow.ToString("dd/MM/yyyy HH:mm:ss")}] -> Worker role started");
var listener = serviceProvider.GetService<IMessageProcessor>();
Console.CancelKeyPress += (sender, eArgs) =>
{
listener.OnStop();
Console.WriteLine($"[{DateTime.UtcNow.ToString("dd/MM/yyyy HH:mm:ss")}] -> Worker role finished");
_quitEvent.Set();
eArgs.Cancel = true;
};
_quitEvent.WaitOne();
}
private static IConfigurationRoot GetConfiguration()
{
// Build appsetting.json configuration
var environment = Environment.GetEnvironmentVariable("Environment");
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment}.json", optional: true)
.AddEnvironmentVariables().Build();
}
private static void ConfigureServices(IServiceCollection services)
{
IConfigurationRoot configuration = GetConfiguration();
services.AddSingleton<IConfigurationRoot>(configuration);
// Support typed options
services.AddOptions();
services.Configure<RabbitMQSettings>(configuration.GetSection("RabbitMQConfig"));
services.AddSingleton<IQueueConsumer, QueueConsumer>();
services.AddScoped<IMessageProcessor, MessageProcessor>();
}
}
For the issue you are trying to get around only thing I can think of is treat this as a transaction.. Only when you get the message, process it completely and send the Ack back consider the transaction as completed.
If you process the message first and some one terminates the application before the Ack it will get queued again and when we restart the application it will get processed again.
Against that if you Ack first and then try to process the message and some one terminates the application you will lose the message.
So looks like considering the whole process as transaction will make it work or the other option is to take care of same message getting processed again.
Related
I am using rabbitmq in a "Work Queues" scenario.
I need eg. a pool of 5 consumers, (each with its own channel), so one consumer doing I/O operations, won't block other consumer of the same queue.
Eg.
If I have on my queue:
Message 1, Message 2, Message 3, Message 4. Each instance of (FistConsumerHandler) will take 1 message from the queue using Round Robin (default rabbitmq behavior)
The problem I am facing is I need to do this using Dependency Injection.
Here is what i have so far:
On Windows service start (my consumers are hosted in a windows service):
protected override void OnStart(string[] args)
{
BuildConnections();
// Register the consumers. For simplicity only showing FirstConsumerHandler.
AddConsumerHandlers<FistConsumerHandler>(ConstantesProcesos.Exchange, ConstantesProcesos.QueueForFirstHandler);
BuildStartup();
var logger = GetLogger<ServicioProcesos>();
logger.LogInformation("Windows Service Started");
Console.WriteLine("Press [enter] to exit.");
}
protected virtual void BuildConnections(
string notificationHubPath = "notificationhub_path",
string rabbitMQHostname = "rabbitmq_hostname",
string rabbitMQPort = "rabbitmq_port",
string rabbitMQUserName = "rabbitmq_username",
string rabbitMQPassword = "rabbitmq_password")
{
ContextHelpers.Setup(ConfigurationManager.ConnectionStrings[appContextConnectionString].ConnectionString);
if (_connection == null)
{
var factory = new ConnectionFactory
{
HostName = ConfigurationManager.AppSettings[rabbitMQHostname],
Port = int.Parse(ConfigurationManager.AppSettings[rabbitMQPort]),
UserName = ConfigurationManager.AppSettings[rabbitMQUserName],
Password = ConfigurationManager.AppSettings[rabbitMQPassword],
DispatchConsumersAsync = true,
};
// Create a connection
do
{
try
{
_connection = factory.CreateConnection();
}
catch (RabbitMQ.Client.Exceptions.BrokerUnreachableException e)
{
Thread.Sleep(5000);
}
} while (_connection == null);
}
_startupBuilder = new StartupBuilder(_connection);
}
protected void AddConsumerHandlers<THandler>(string exchange, string queue)
{
var consumerHandlerItem = new ConsumerHandlerItem
{
ConsumerType = typeof(THandler),
Exchange = exchange,
Queue = queue
};
_startupBuilder._consumerHandlerItems.Add(consumerHandlerItem);
}
protected void BuildStartup()
{
ServiceProvider = _startupBuilder.Build();
}
Startup Builder:
using Microsoft.Extensions.DependencyInjection;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
public class StartupBuilder
{
private static IConnection _connection;
private IModel _channel;
public List<ConsumerHandlerItem> _consumerHandlerItems;
public IServiceCollection Services { get; private set; }
public StartupBuilder(IConnection connection)
{
_connection = connection;
_consumerHandlerItems = new List<ConsumerHandlerItem>();
Services = new ServiceCollection();
}
public IServiceProvider Build()
{
_channel = _connection.CreateModel();
Services.InitSerilog();
// Add channel as singleton (this is not correct as I need 1 channel per ConsumerHandler)
Services.AddSingleton(_channel);
// Register the ConsumerHandler to DI
foreach (var item in _consumerHandlerItems)
{
// Add FirstHandler to DI
Type consumerType = item.ConsumerType;
Services.AddSingleton(consumerType);
}
// Finish DI Setup
var serviceProvider = Services.BuildServiceProvider();
// Bind the consumer handler to the channel and queue
foreach (var item in _consumerHandlerItems)
{
var consumerHandler = (AsyncEventingBasicConsumer)serviceProvider.GetRequiredService(item.ConsumerType);
_channel.AssignNewProcessor(item, consumerHandler);
}
return serviceProvider;
}
}
Helpers:
public static class QueuesHelpers
{
public static void AssignNewProcessor(this IModel channel, ConsumerHandlerItem item, AsyncEventingBasicConsumer consumerHandler)
{
channel.ExchangeDeclare(item.Exchange, ExchangeType.Topic, durable: true);
channel.QueueDeclare(item.Queue, true, false, false, null);
channel.QueueBind(item.Queue, item.Exchange, item.Queue, null);
channel.BasicConsume(item.Queue, false, consumerHandler);
}
}
Consumer handler:
public class FistConsumerHandler : AsyncEventingBasicConsumer
{
private readonly ILogger<FistConsumerHandler> _logger;
private Guid guid = Guid.NewGuid();
public FistConsumerHandler(
IModel channel,
ILogger<FistConsumerHandler> logger) : base(channel)
{
Received += ConsumeMessageAsync;
_logger = logger;
}
private async Task ConsumeMessageAsync(object sender, BasicDeliverEventArgs eventArgs)
{
try
{
// consumer logic to consume the message
}
catch (Exception ex)
{
}
finally
{
Model.Acknowledge(eventArgs);
}
}
}
The problem with this code is:
There is ony 1 instance of FistConsumerHandler (as is reigstered as singleton). I need, for instance 5.
I have only 1 channel, I need 1 channel per instance.
To sum up, the expected behavior using Microsoft.Extensions.DependencyInjection should be:
Create a connection (share this connection with all consumers)
When a message is received to the queue, it should be consumed by 1 consumer using its own channel
If another message is received to the queue, it should be consumed by another consumer
TL;DR; Create your own scope
I've done something similar in an app I'm working on, albeit not as cleanly as I would like (and thus why I came across this post). The key for me was using IServiceScopeFactory to get injected services and use them in a consumer method. In a typical HTTP request the API will automatically create/close scope for you as the request comes in / response goes out, respectively. But since this isn't an HTTP request, we need to create / close the scope for using injected services.
This is a simplified example for getting an injected DB context (but could be anything), assuming I've already set up the RabbitMQ consumer, deserialized the message as an object (FooEntity in this example):
public class RabbitMQConsumer
{
private readonly IServiceProvider _provider;
public RabbitMQConsumer(IServiceProvider serviceProvider)
{
this._serviceProvider = serviceProvider;
}
public async Task ConsumeMessageAsync()
{
// Using statement ensures we close scope when finished, helping avoid memory leaks
using (var scope = this._serviceProvider.CreateScope())
{
// Get your service(s) within the scope
var context = scope.ServiceProvider.GetRequiredService<MyDBContext>();
// Do things with dbContext
}
}
}
Be sure to register RabbitMQConsumer as a singleton and not a transient in Startup.cs also.
References:
Similar SO post
MS Docs
I have a RabbitMQ client program written in C#. While the application works in console based application (because execution is blocked by Console.ReadLine) it does not work in Windows Form based application. In Windows Form application, execution doesn't wait on Console.ReadLine and terminates on completion. I am looking for solution where my listener keeps monitoring for new messages from server without being terminated.
Here is the the client code :
try {
var factory = new ConnectionFactory() { HostName = "xxx" , UserName ="xxx", Password="xxx"};
using(var connection = factory.CreateConnection())
using(var channel = connection.CreateModel())
{
channel.ExchangeDeclare(exchange: "call_notify", type: "fanout");
var queueName = channel.QueueDeclare().QueueName;
channel.QueueBind(queue: queueName,
exchange: "call_notify",
routingKey: "");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(message);
};
channel.BasicConsume(queue: queueName,
autoAck: true,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine(); // Program does'nt wait here in windows form based application
}
}
Don't use using since that will dispose of everything immediately
Store the needed objects (connection? channel? consumer?) in class fields rather than local variables
No need for threads since the objects handle things asynchronously. Just create the objects and that's it
Close/dispose of the objects when the application terminates or you need to stop listening
This way they'll live until the application terminates.
If you want the code to work on both platforms, you'll be much better off creating an abstraction layer that exposes the messages and deals with the start/stop logic.
public class RabbitMQManager : IDisposable
{
private bool _disposed = false;
private IModel _channel;
private IConnection _connection;
public event EventHandler<string> MessageReceived;
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_channel?.Dispose();
_connection?.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
}
public void Connect()
{
var factory = new ConnectionFactory { HostName = "xxx", UserName = "xxx", Password = "xxx" };
_connection = factory.CreateConnection();
_channel = _connection.CreateModel();
_channel.ExchangeDeclare(exchange: "call_notify", type: "fanout");
string queueName = _channel.QueueDeclare().QueueName;
_channel.QueueBind(queue: queueName,
exchange: "call_notify",
routingKey: "");
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += (model, ea) =>
{
byte[] body = ea.Body;
string message = Encoding.UTF8.GetString(body);
MessageReceived?.Invoke(this, message);
};
_channel.BasicConsume(queue: queueName,
autoAck: true,
consumer: consumer);
}
}
You can then use this in any project type you want by creating an instance of the class and subscribing to the MessageReceived event. For example, a WinForms implementation would be just:
public class MyForm : Form
{
private RabbitMQManager _rabbitMQManager;
public MyForm() { _rabbitMQManager = new RabbitMQManager(); }
// you can call this from constructor or some event
public void Connect()
{
_rabbitMQManager.MessageReceived = (sender, msg) => someLabel.Text = msg;
_rabbitMQManager.Connect();
}
}
Look at this Q&A to know how to override MyForm.Dispose so that resources are properly disposed: How do I extend a WinForm's Dispose method?
And in a console application, this could just be:
using (var manager = new RabbitMQManager())
{
manager.MessageReceived += (sender, msg) => Console.WriteLine(msg);
manager.Connect();
Console.Read();
}
Thanks everyone! With all your suggestions i have found multiple ways to accomplish this.
The way i implemented this is by making Factory, Connection and Channel as class variables and have defined them in MainForm constructor. This way objects are preserved and program keeps listening for incoming messages.
I have a receiver method in an ASP.NET Core web app:
public void ReceiveMessage()
{
using (var connection = CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: "QueueName",durable: false,exclusive: false,autoDelete: false,arguments: null);
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
//Do something
channel.BasicAck(deliveryTag: ea.DeliveryTag,multiple: false);
};
channel.BasicConsume(queue: "QueueName",autoAck: false,consumer: consumer);
//BAD!!!
while (true)
{
}
}
}
You'll notice I am using while(true) which smells bad. I basically need to keep this method alive and wonder how have other people done it?
This method should stay alive all the time and automatically process messages 1 by 1
You can create a Hosted service and consume your message inside it. This Hosted service is always alive and can receive all the messages.
public class ProductTopicConsumerService : ConsumerBase, IHostedService
{
public ProductTopicConsumerService(ConnectionFactory connectionFactory)
: base(connectionFactory, ExchangeTypes.topic)
{
try
{
Consume<ProductCreatedIntegrationEvent>();
}
catch (Exception ex)
{
Console.WriteLine($"Error => {ex?.Message}");
}
}
protected override string Exchange => "ProductExchangeTopic5";
protected override string Queue => "Product.Updated5";
protected override string AppId => "ProductCreatedConsumer";
protected override string QueueAndExchangeRoutingKey => "Product.*";
public virtual Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public virtual Task StopAsync(CancellationToken cancellationToken)
{
Dispose();
return Task.CompletedTask;
}
}
and ConsumerBase could be like this :
public abstract class ConsumerBase : RabbitMqClientBase
{
public ConsumerBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType)
: base(connectionFactory, exchangeType,false)
{
}
protected void Consume<TMessage>()
{
var consumer = new AsyncEventingBasicConsumer(Channel);
consumer.Received += OnEventReceived<TMessage>;
Channel.BasicConsume(queue: RefinedQueueName, autoAck: false, consumer: consumer);
}
private Task OnEventReceived<TMessage>(object sender, BasicDeliverEventArgs #event)
{
try
{
var body = Encoding.UTF8.GetString(#event.Body.ToArray());
var message = JsonConvert.DeserializeObject<TMessage>(body);
ReceiveAction(message);
Channel.BasicAck(#event.DeliveryTag, false);
return Task.CompletedTask;
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred in OnEventReceived. Message: {ex?.Message}");
Channel.BasicNack(#event.DeliveryTag, false, true);
throw;
}
finally
{
}
}
protected virtual void ReceiveAction<TMessage>(TMessage message)
{
}
}
and finally, rabbitMQBase could be something like this :
public abstract class RabbitMqClientBase : IDisposable
{
protected const string VirtualHost = "MQ";
protected abstract string Exchange { get; }
protected abstract string Queue { get; }
protected abstract string AppId { get; }
protected abstract string QueueAndExchangeRoutingKey { get; }
protected IModel Channel { get; private set; }
private IConnection _connection;
private readonly ConnectionFactory _connectionFactory;
private readonly ExchangeTypes _exchangeType;
private readonly bool _isPublisher;
protected RabbitMqClientBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType, bool isPublisher)
{
_connectionFactory = connectionFactory;
this._exchangeType = exchangeType;
this._isPublisher = isPublisher;
ConnectToRabbitMq();
}
protected internal string RefinedExchangeName => $"{VirtualHost}.{Exchange}";
protected internal string RefinedQueueName => $"{VirtualHost}.{Queue}";
protected internal string RefinedRoutingKey => $"{VirtualHost}.{QueueAndExchangeRoutingKey}";
private void ConnectToRabbitMq()
{
if (_connection == null || _connection.IsOpen == false)
{
_connection = _connectionFactory.CreateConnection();
}
if (Channel == null || Channel.IsOpen == false)
{
Channel = _connection.CreateModel();
Channel.ExchangeDeclare(exchange: RefinedExchangeName, type: _exchangeType.ToString(), durable: true, autoDelete: false);
if (!_isPublisher)
{
Channel.QueueDeclare(queue: RefinedQueueName, durable: true, exclusive: false, autoDelete: false);
Channel.QueueBind(queue: RefinedQueueName, exchange: RefinedExchangeName, routingKey: RefinedRoutingKey);
}
}
}
public void Dispose()
{
Channel?.Close();
Channel?.Dispose();
Channel = null;
_connection?.Close();
_connection?.Dispose();
_connection = null;
}
}
and this is Publisher|Prodcuer class for RabbitMQ :
public interface IRabbitMqProducer<in T>
{
PublishResult Publish(T #event);
}
public abstract class ProducerBase<T> : RabbitMqClientBase, IRabbitMqProducer<T>
{
protected ProducerBase(ConnectionFactory connectionFactory, ExchangeTypes exchangeType)
: base(connectionFactory, exchangeType, true) { }
public PublishResult Publish(T #event)
{
var result = new PublishResult();
try
{
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(#event));
var properties = Channel.CreateBasicProperties();
properties.AppId = AppId;
properties.ContentType = "application/json";
properties.DeliveryMode = 2; //persist mode
properties.Timestamp = new AmqpTimestamp(DateTimeOffset.UtcNow.ToUnixTimeSeconds());
Channel.BasicPublish(exchange: RefinedExchangeName, routingKey: RefinedRoutingKey, basicProperties: properties, body: body);
result.SetSuccess();
}
catch (Exception ex)
{
result.SetError(ex);
}
return result;
}
}
and for publishing your publisher should be like this:
public class ProductTopicProducerService : ProducerBase<ProductCreatedIntegrationEvent>
{
public ProductTopicProducerService(ConnectionFactory connectionFactory)
: base(connectionFactory,ExchangeTypes.topic)
{
}
protected override string Exchange => "ProductExchangeTopic5";
protected override string Queue => "";
protected override string AppId => "ProductCreatedConsumer";
protected override string QueueAndExchangeRoutingKey => "Product.*";
}
and for publish a new message follow the below code:
var result = _producer.Publish(#event);
if (!result.IsSuccess)
{
Console.WriteLine($"Error => {result.Description}");
}
Offsource you can simplify your consumer. but this is my experience to create a base class for rabbitMQBase and now both Publisher and consumer could use it.
Take a look at the OWIN package - available in NuGet: Microsoft.Owin.Hosting
When you use it you are self-hosting the web service and your app would start with a call to:
WebApp.Start(queryServiceUrl, Startup);
Where "Startup" is a method to do all your initialization. Your app/service stays running and will accept queries on the specified URL.
Without a loop, the instant after you call channel.BasicConsume, the whole thing (connection/channel) will go out of scope and be immediately disposed/destroyed via the using statement. So, without the loop, your consumer doesn't actually consume anything.
To ensure the consumer operates, you need to have an infinite loop, with appropriate logic to exit when you shut down the program. This is an unfortunate design of the .NET library for RabbitMQ, but it is what it is.
while (_isRunning & channel.IsOpen) {
Thread.Sleep(1);
// Other application logic here; e.g. periodically break out of the
// loop to prevent unacknowledged messages from accumulating in the system
// (if you don't, random effects will guarantee that they eventually build up)
}
Using RabbitMQ in a very simple console app I'm running into a problem where the console app isn't quiting at the end of execution and I think it might be because I'm hanging on to something in RabbitMQ. So what exactly do I need to dispose of? I have a class Queue class that looks something like this:
public class ManagementQueue : IDisposable
{
private IConnection connection;
private IModel channel;
private string privateQueueName;
private EventingBasicConsumer consumer;
public event EventHandler SomeEvents;
public ManagementQueue()
{
var factory = new ConnectionFactory()
{
HostName = Properties.Settings.Default.RabbitMQServer,
UserName = Properties.Settings.Default.RabbitMQUser,
Password = Properties.Settings.Default.RabbitMQPassword
};
connection = factory.CreateConnection();
channel = connection.CreateModel();
privateQueueName = channel.QueueDeclare().QueueName;
channel.QueueDeclare(queue: Properties.Settings.Default.ManagementQueueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.ExchangeDeclare(Properties.Settings.Default.BroadcastExchange, "fanout");
channel.ExchangeDeclare(Properties.Settings.Default.DirectExchange, "direct");
channel.QueueBind(privateQueueName,
Properties.Settings.Default.DirectExchange,
privateQueueName);
channel.QueueBind(Properties.Settings.Default.ManagementQueueName,
Properties.Settings.Default.DirectExchange,
"fromManager");
channel.BasicQos(0, 1, false);
}
public void StartListening()
{
consumer = new EventingBasicConsumer(channel);
consumer.Received += ReceivedHandler;
channel.BasicConsume(queue: privateQueueName,
noAck: true,
consumer: consumer);
}
public void StopListening()
{
consumer.Received -= ReceivedHandler;
}
private void ReceivedHandler(object model, BasicDeliverEventArgs ea)
{
// Do some stuff...
}
public void SomeFunction()
{
// send out some message...
var props = channel.CreateBasicProperties();
props.ReplyTo = privateQueueName;
channel.BasicPublish(exchange: Properties.Settings.Default.BroadcastExchange,
routingKey: "",
basicProperties: props,
body: null);
}
public void Dispose()
{
if (channel != null)
{
channel.Dispose();
channel = null;
}
if (connection != null)
{
connection.Dispose();
connection = null;
}
}
}
And in the console I have something like:
static void Main(string[] args)
{
if (!args.Any())
{
// This works fine exiting here
return;
}
var queue = new ManagementQueue();
queue.SomeEvents += (o,e) => { // do something };
queue.StartListening();
queue.SomeFunction();
while (!somecondition)
{
// do some other stuff
}
queue.StopListening();
queue.Dispose();
return; // app should quit here, but doesn't???
}
I'm suspicious that it might be something going on with the consumer, but there doesn't appear to be any methods to dispose, close or disconnect it. No where am I explicitly creating any new threads or any tight loops (other than the while loop, but that does exit when the condition evaluates to false).
This is a pretty old post, but it looks like RabbitMQ has had issues with calling Dispose on IModel. You may want to just try disposing of the connection and seeing if this resolves the issue.
Ref1: http://markmail.org/message/tqyiezrmixagtwa4#query:+page:1+mid:mhl5iw2gylwznd5o+state:results
Ref2: http://rabbitmq.1065348.n5.nabble.com/IModel-Dispose-locks-if-called-during-ConnectionShutdown-event-tc22104.html
Proposed Fix:
The suggestion is to not call dispose on the IModel instance during the
ConnectionShutdown event. I had to reword my code to track if it is being
called in a connectionshutdown or not to know when to call dispose.
I am trying to connect from my C# code to IBM MQ.
Callback works for one listener (listening to a queue). i.e. when a message comes to the queue, callback method is correctly invoked.
I created another queue#2 on the same QueueManager / Channel. Registered a new listener on this queue with a different callback method. No errors are observed during server startup.
When a message arrives at this queue#2, callback associated with the first queue is invoked and not the one that I have registered for this queue. I tried by creating a different session, and also a different connection, but still the behaviour is the same.
Does anybody have any ideas?
class Program
{
static void Main(string[] args)
{
string env = "DEV"
string queueName1= "Queue1"
string queueName2 = "Queue2"
new MyListener(CallbackHandler1.onMessage1, env, queueName1).RegisterListener();
new MyListener(CallbackHandler2.onMessage2, env, queueName2).RegisterListener();
}
public class MyListener
{
public delegate void Handler (IMessage msg)
public static Handler _handler
private string env = "";
private string queue = "";
public MyListner(Handler _handler, string environment, string queueName)
{
_handler = _handler;
this.env = environment;
this.queue = queueName
}
public void RegisterListener()
{
XMSFactoryFactory xff = XMSFactoryFactory.GetInstance(XMSC.CT_WMQ);
IConnectionFactory cf = xff.CreateConnectionFactory();
cf.SetStringProperty(XMSC.WMQ_HOST_NAME, "10.87.188.156(7111)");
cf.SetIntProperty(XMSC.WMQ_PORT, 7111);
cf.SetStringProperty(XMSC.WMQ_CHANNEL, "QMEIGS1.CRM.SVRCONN");
cf.SetIntProperty(XMSC.WMQ_CONNECTION_MODE, XMSC.WMQ_CM_CLIENT_UNMANAGED);
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, "QMEIGS1");
IConnection conn = cf.CreateConnection();
Console.WriteLine("connection created");
ISession sess = conn.CreateSession(false, AcknowledgeMode.AutoAcknowledge);
IDestination dest = sess.CreateQueue(queue);
IMessageConsumer consumer = sess.CreateConsumer(dest);
MessageListener ml = new MessageListener(OnMessage);
consumer.MessageListener = ml;
conn.Start();
Console.WriteLine("Consumer started");
}
private void onMessage(IMessage m)
{
try {
_handler(m)
}
catch (Exception e )
{
}
}
}
//callback 1
public class CallbackHandler1
public static void onMessage1(IMessage msg)
{
ITextMessage textMessage = IMessage(msg)
// code to perform onmessage1
}
//callback 2
public class CallbackHandler2
public static void onMessage2(IMessage msg)
{
ITextMessage textMessage = IMessage(msg)
// code to perform onmessage2
}
I am not a hardcore c# programmer but I think the problem is because of the static keyword in public static Handler _handler;. The keyword static makes only one instance of _handler available across all instances of a MyListener class. Although you are creating two consumers and assigning two listeners, the second call to assign the message listener is overwriting the first listener. Hence you are receiving messages from only one queue.
I modified your code little bit as it was not compiling and got it working after removing the static keyword. I used MQ v8 for compiling the program. But the version should not be a problem for you.
namespace multilistener
{
class Program
{
static void Main(string[] args)
{
string env = "DEV";
string queueName1= "SUB.Q";
string queueName2 = "SUB.Q1";
new MyListener(CallbackHandler1.onMessage1, env, queueName1).RegisterListener();
new MyListener(CallbackHandler2.onMessage2, env, queueName2).RegisterListener();
System.Threading.Thread.Sleep(30000);
Console.WriteLine("Program ends");
}
}
public class MyListener
{
public delegate void Handler (IMessage msg);
public Handler _handler; // Remove 'static' keyword
private string env = "";
private string queue = "";
public MyListener(Handler _Inhandler, string environment, string queueName)
{
_handler = _Inhandler;
this.env = environment;
this.queue = queueName;
}
public void RegisterListener()
{
try
{
XMSFactoryFactory xff = XMSFactoryFactory.GetInstance(XMSC.CT_WMQ);
IConnectionFactory cf = xff.CreateConnectionFactory();
cf.SetStringProperty(XMSC.WMQ_HOST_NAME, "localhost");
cf.SetIntProperty(XMSC.WMQ_PORT, 1414);
cf.SetStringProperty(XMSC.WMQ_CHANNEL, "MY.SVRCONN");
cf.SetIntProperty(XMSC.WMQ_CONNECTION_MODE, XMSC.WMQ_CM_CLIENT);
cf.SetStringProperty(XMSC.USERID, "userid");
cf.SetStringProperty(XMSC.PASSWORD, "password");
cf.SetStringProperty(XMSC.WMQ_QUEUE_MANAGER, "QM1");
IConnection conn = cf.CreateConnection();
Console.WriteLine("connection created");
ISession sess = conn.CreateSession(false, AcknowledgeMode.AutoAcknowledge);
IDestination dest = sess.CreateQueue(queue);
IMessageConsumer consumer = sess.CreateConsumer(dest);
MessageListener ml = new MessageListener(onMessage);
consumer.MessageListener = ml;
conn.Start();
Console.WriteLine("Consumer started");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private void onMessage(IMessage m)
{
try {
_handler(m);
}
catch (Exception e )
{
Console.Write(e);
}
}
}
//callback 1
public class CallbackHandler1
{
public static void onMessage1(IMessage msg)
{
ITextMessage textMessage = (ITextMessage)msg;
// code to perform onmessage1
Console.WriteLine("First consumer");
}
}
//callback 2
public class CallbackHandler2
{
public static void onMessage2(IMessage msg)
{
ITextMessage textMessage = (ITextMessage)msg;
// code to perform onmessage2
Console.WriteLine("Second consumer");
}
}
}