how in BOOST send a signal in a thread and have the corresponding slot executed in another thread?

时光毁灭记忆、已成空白 提交于 2019-12-17 15:51:45

问题


In Qt for instance if you emit a signal in a thread other that the GUI thread, the signal is enqueued and executed later in the GUI thread, is there a way to do that with boost?

thanks


回答1:


For an event loop use boost::asio::io_service. You can post tasks inside this object and have another thread execute them, in a thread safe way:

struct MyClass
{
    boost::io_service service;
    void doSomethingOp() const { ... }

    void doSomething()
    {
        service.post(boost::bind(&MyClass::doSomethingOp, this));
    }

    void loop()
    {
            service.run(); // processes the tasks
    }
};

boost::signal<void()> mySignal;

MyClass myClass;
mySignal.connect(boost::bind(&MyClass::doSomething, boost::ref(myClass)));

// launches a thread and executes myClass.loop() there
boost::thread t(boost::bind(&MyClass::loop(), boost::ref(myClass)));

// calls myClass.doSomething() in this thread, but loop() executes it in the other
mySignal(); 



回答2:


Not directly, because boost does not provide an event loop.

To have a signal handled in another thread, that another thread needs to be checking the queue of handlers it should run and execute them (which usually means some kind of event-loop). Boost does not provide one, so you'll need to get it from elsewhere or write it.

If you have an event-loop, that does not provide signals, (or implement some simple solution with queues) you should be able to (ab)use boost.signals2 (not boost.signals, because that version is not thread-safe) by overriding the operator+= to wrap each handler in something, that will queue it for execution in the other thread. You might even be able to implement it for signals with return values (which is not supported by Qt, but is supported by boost), but you'll have to be careful to avoid dead-lock.




回答3:


Here's a complete example of the above mentioned io_service, executor_work_guard, signals2::signal.

  • io_service is the event loop handler
  • executor_work_guard make sure the m_service.run() doesn't only execute once
  • signal/slot decouples the sender and receiver
  • the thread runs all the process of the io_service
#include <boost/thread.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/signals2/signal.hpp>

class IOService
{
public:
  IOService() : m_worker(boost::asio::make_work_guard(m_service)) {}
  ~IOService() {}

  // slot to receive signal
  void slotMessage(std::string msg)
  {
    m_service.post(boost::bind(&IOService::process, this, msg));
  }

  // start/close background thread
  bool start()
  {
    if (m_started)
      return true;
    m_started = true;

    // start reader thread
    m_thread = boost::thread(boost::bind(&IOService::loop, this));
    return m_started;
  }

  void loop()
  {
    m_service.run();
  }

  void close()
  {
    m_worker.reset();
    if (m_thread.joinable())
      m_thread.join();
    m_started = false;
  }

  // process
  void process(std::string msg)
  {
    printf("process %s\n", msg.c_str());
  }

private:
  bool m_started = false;
  boost::asio::io_service m_service;
  boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_worker;
  boost::thread m_thread;
};

int main()
{
  // service instance
  IOService serv;
  serv.start();

  // signal to slot
  boost::signals2::signal<void(std::string)> signalMessage;
  signalMessage.connect(boost::bind(&IOService::slotMessage, boost::ref(serv), _1));

  // send one signal
  signalMessage("abc");

  // wait and quit
  boost::this_thread::sleep(boost::chrono::seconds(2));
  serv.close();
}



回答4:


Chila's answer is correct, but it's missing one important thing: A boost::thread object will only call the function its passed once. Since the boost::io_service has no work to do until the signal is emitted, the thread will finish immediately. To counter this there is a boost::asio::io_service::work class. Before you call the run() method of the io_service you should create a work object and pass it the io_service:

//as a class variable
std::shared_ptr<boost::asio::io_service::work> worker;

//before you call run() of the io_service yourIOService
worker = std::make_shared<boost::asio::io_service::work>(yourIOService);

//If you want the service to stop
worker.reset();

Note: At the time of writing (boost 1.67) this method is already deprecated and you are supposed to use io_context::executor_work_guard (basically same functionality as io_service::work). I was not able to compile when using the new method though, and the work solution is still working in boost 1.67.




回答5:


For some reason, the assignment operator of boost::asio::executor_work_guard<boost::asio::io_context::executor_type> is deleted, but you still can construct it.

Here's my version of the code that posts some movable Event object and processes it on the thread running io_context::run():

class MyClass {
public:
  MyClass () : m_work(boost::asio::make_work_guard(m_service)) {}

  size_t loop() {
    return m_service.run();
  }

  void stop() {
    m_work.reset();
  }

  void doSomething(Event&& e) {
    m_service.post([this, e=std::move(e)]{ doSomethingOp(std::move(e)); });
  }

private:
  void doSomethingOp(const Event& event) {
    ...
  }
//data:
  boost::asio::io_context m_service;
  boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_work;
};

It requires C++14 and was tested with VS2017 and GCC 6.4 with thread & memory sanitizers.



来源:https://stackoverflow.com/questions/5050588/how-in-boost-send-a-signal-in-a-thread-and-have-the-corresponding-slot-executed

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!