10

I'm trying to create a C# Winform application that uses ZeroMQ (clrzmq .net bindings (x86) via nuget) in a pub/sub model.

After much searching, I can only find standalone C# examples where the code uses a while statement to process new messages indefinitely. When I try to use these examples, I don't know where to put the code, and it just blocks the gui and everything else.

I don't know if it's impossible to do without using another thread, but I was under the impression that ZeroMQ's asynchronous behaviors could work without coding extra threads. Perhaps I just don't know where to put the zeromq code, or perhaps I really do need another thread.

If someone could provide a simple pub/sub example with directions of where to actually insert the code into a default C# winform application it would be very appreciated.

uberdanzik
  • 549
  • 1
  • 7
  • 18

1 Answers1

8

I am assuming you are using the clrzmq ZeroMq wrapper in your project. As far as I know it is not possible to receive message non-blocking in a simple loop using clrzmq, it will block either indefinitely, for a specific amount of time (by supplying a timeout to the receive method) or until you receive a message.

However, it is fairly trivial to set up a thread to poll the socket periodically and push incoming messages to onto a Queue. You may then use for example a simple WinForms Timer to periodically dequeue any pending messages from that (shared) Queue. Here is a working example of a threaded subscriber:

public class ZeroMqSubscriber
{
    private readonly ZmqContext _zmqContext;
    private readonly ZmqSocket _zmqSocket;
    private readonly Thread _workerThread;
    private readonly ManualResetEvent _stopEvent = new ManualResetEvent(false);
    private readonly object _locker = new object();
    private readonly Queue<string> _queue = new Queue<string>();

    public ZeroMqSubscriber(string endPoint)
    {
        _zmqContext = ZmqContext.Create();
        _zmqSocket = _zmqContext.CreateSocket(SocketType.SUB);
        _zmqSocket.Connect(endPoint);
        _zmqSocket.SubscribeAll();

        _workerThread = new Thread(ReceiveData);
        _workerThread.Start();
    }

    public string[] GetMessages()
    {
        lock (_locker)
        {
            var messages = _queue.ToArray();
            _queue.Clear();
            return messages;
        }
    }

    public void Stop()
    {
        _stopEvent.Set();
        _workerThread.Join();
    }

    private void ReceiveData()
    {
         try
         {
             while (!_stopEvent.WaitOne(0))
             {
                 var message = _zmqSocket.Receive(Encoding.UTF8, 
                               new TimeSpan(0, 0, 0, 1));
                 if (string.IsNullOrEmpty(message))
                     continue;

                 lock (_locker)
                     _queue.Enqueue(message);
             }
         }
         finally
         {
             _zmqSocket.Dispose();
             _zmqContext.Dispose();
         }
    }
}

From the Form you simply poll the Queue periodically (this example uses a Forms Timer and simply appends the message data to a Textbox):

private readonly ZeroMqSubscriber _zeroMqSubscriber = 
        new ZeroMqSubscriber("tcp://127.0.0.1:5000");

void ReceiveTimerTick(object sender, EventArgs e)
{
    var messages = _zeroMqSubscriber.GetMessages();
    foreach (var message in messages)
        _textbox.AppendText(message + Environment.NewLine);
}
Jakob Möllås
  • 4,239
  • 3
  • 33
  • 61
  • When I try to use your ZeroMqSubscriber class above, Visual Studio says it can't find the namespace or name for "ZmqContext" and some other class instances you use. Yes I have "using ZMQ". Any ideas? – uberdanzik Feb 11 '13 at 18:53
  • Do you use the clrzmq ZeroMq .NET binding? I have `using ZeroMQ`, not ZMQ. The example includes a link to the clrzmq project (in the beginning of the post). I am not sure why the examples on the main page says ZMQ, I think that is the old namespace name. The [ZeroMq .NET examples](https://github.com/imatix/zguide/tree/master/examples/C%23) use `using ZeroMQ`. – Jakob Möllås Feb 11 '13 at 20:42
  • Yep, the [latest clrzmq source](https://github.com/zeromq/clrzmq/blob/master/src/ZeroMQ/ZmqContext.cs) use **ZeroMQ** as the namespace. – Jakob Möllås Feb 11 '13 at 20:49
  • I'm using the clrzmq .net bindings package (x86) via nuget...supposedly this was the recommended way to "get started". I guess there's some differences. – uberdanzik Feb 11 '13 at 22:54
  • 1
    Ok -- So Visual Studio's nuget package manager only shows you version 2.0 unless you change the dropdown from "stable only" to "include pre-release", then version 3.0 appears for update, and voila the namespace switches to ZeroMQ and BAM! no more namespace errors. – uberdanzik Feb 11 '13 at 23:09
  • Confirmed, your code works awesomely with version 3.0 - thank you for your help on this. – uberdanzik Feb 11 '13 at 23:32
  • Great to hear! Just a small note; either make sure you can dequeue incoming data faster than it is received (by polling the queue constantly) or add a safeguard to prevent the queue from growing indefinitely, i.e. create your own "high water mark". Otherwise the queue will fill until the application crash with an OutOfMemoryException. Good luck! – Jakob Möllås Feb 12 '13 at 08:41
  • Any way to avoid using "lock"? One of the reasons to use zeromq is to avoid lock code. – Adam Dymitruk Feb 25 '13 at 19:06
  • @AdamDymitruk In this case I do not see much point. The question is about integrating ZeroMq into a WinForm application and as such there should not be much overhead of the lock statement in regards to whatever needs to be done in the Gui when messages are received. Updating Gui controls (for example adding incoming messages to a list) is much slower than the lock statement. At some point the data needs to be integrated into the Gui thread. One option could be to use a ConcurrentQueue (.NET 4). Not sure if that is faster though. It may lock behind the scenes. – Jakob Möllås Feb 25 '13 at 19:33
  • I'm not so much concerned about speed as the cleanliness of code. From the guide: "It handles I/O asynchronously, in background threads. These communicate with application threads using lock-free data structures, so concurrent ØMQ applications need no locks, semaphores, or other wait states." and from multithreading section that is in bold: "To make utterly perfect MT programs (and I mean that literally) we don't need mutexes, locks, or any other form of inter-thread communication except messages sent across ØMQ sockets." Hence my reaction to seeing the "lock" word in your example. – Adam Dymitruk Feb 25 '13 at 19:41
  • @AdamDymitruk Yes, that is correct. And normally that is just the case; you pass messages via ZeroMq (between threads), not via lock-protected producer/consumer queues like in my example. However, since the question is about a Gui application having a Gui thread serving the Gui, something has to give. You cannot both have a fluid Gui and a blocked reader in the same thread. Although you can poll the ZeroMq socket regularly, using a short timeout (from the Gui thread), but I still do not know if that is the best solution. Normally the Gui would just handle events from thread handling the IO. – Jakob Möllås Feb 25 '13 at 22:13
  • understood.. hope others do too. – Adam Dymitruk Feb 25 '13 at 22:57