4

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
            }
        }
Faiz
  • 57
  • 3
  • Windows applications and console applications are not mutually exclusive... – Trevor May 26 '19 at 06:31
  • Clicking the Close button of the main window in a Winforms app is equivalent to pressing Enter in the console mode app. It isn't clear why you clicked it. If you don't create the Form object and just use Application.Run() without an argument then it will run forever and can only be terminated with Task Manager. High odds that you ought to make this a service instead. – Hans Passant May 26 '19 at 07:14

3 Answers3

4
  1. Don't use using since that will dispose of everything immediately
  2. Store the needed objects (connection? channel? consumer?) in class fields rather than local variables
  3. No need for threads since the objects handle things asynchronously. Just create the objects and that's it
  4. Close/dispose of the objects when the application terminates or you need to stop listening

This way they'll live until the application terminates.

Sami Kuhmonen
  • 30,146
  • 9
  • 61
  • 74
  • Correct and I concur, but how does this address console and winforms? How do you wait in either solution? – Trevor May 26 '19 at 06:33
  • In console app if user removes the readline, the console app exits right away, but winforms it does'nt work... `This way they'll live until the application terminates` I dont agree, it depends on implementation of these objects... – Trevor May 26 '19 at 06:40
  • @Çöđěxěŕ No, the objects will live on. They can't be destroyed since there's something holding on to them. The connections might not, of course, but that's another matter. And there wasn't any question about console, since that has already been handled. The same code can be used there also, with the addition of not ending the application, which they know how to do. There's no need to wait, only to keep the objects alive. – Sami Kuhmonen May 26 '19 at 06:51
  • To make more sense, i have a UI created and in MainUIForm constructor i am calling this method which creates a connection and listens for incoming messages. Somehow this doesn't works. Looks like connection are destroyed – Faiz May 26 '19 at 07:22
2

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();
}
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
-2

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.

Faiz
  • 57
  • 3