c++multithreadingboost-signals2

call boost signals2 slot on a separate thread


How can one call a boost signals2 slot on a separate thread without blocking the thread that is emitting the signal? I would like to get the functionality of the Qt5 QObject::connect with the QueuedConnection argument; for Qt5 details see Threads and QObjects. That is, I need to be able to emit a boost signals2 signal with no possiblity of blocking the thread that is emitting the signal; the slots that are connected are called from a separate "event" thread.

I like the functionality of the Qt5 API, but I cannot have QObject as a base class and the additional MOC machinery in my API and implementation. Boost signals2 is thread safe in its connection handling, but it is not multithreaded (really I am just looking for non-blocking) in its calls to connected slots.

I believe a combination of boost signals2 and asio as an event loop could do what I need, but I am unsure how to implement this. Plus, I am sure other people have had similar needs, so I am looking for ideas and suggestions on how to achieve this.

Thanks!


Solution

  • boost::signals2::signal invoke slots sequentially on the thread that emits the signal. If you wish to achieve a non blocking behavior you would simply have to emit the signal on a different thread yourself.

    From what I understand, Qt QueuedConnection not only means the slot is invoked asynchronously, but on the receiver specific thread event loop. This is different from invoking the slot on a arbitrary thread. Your intent is not 100% clear to me.

    Assuming you don't care on which thread the slot is invoked, so long as it does not block, you could wrap your signal in a small object that posts to a different thread.

    A simple example, using boost::asio:

    struct async_signal : std::enable_shared_from_this<async_signal>
    {
        boost::signals2::signal<void(void)> signal;
        boost::asio::io_context             executor;
    
        void emit_async()
        {
            boost::asio::post(excutor, [self_weak = weak_from_this()]()
                {
                   if (auto self = self_weak.lock())
                   {
                      signal();
                   }
                });
        }
    }
    

    Now the slots will be invoked sequentially on whichever thread(s) that call io_context::run(). This is usually a thread pool of some sort such as boost::asio::thread_pool;

    If you are wondering about the std::enable_shared_from_this part, this is for thread safety.

    You could of course make this class templated and well encapsulated, but I think its easier to understand this way.