如何等待asio处理程序?

时间:2013-12-20 18:22:08

标签: c++ boost boost-asio synchronous c++03

我有一个围绕boost::asio::io_service的对象,它有一些属性。这样的事情:

class Foo
{
  private:

    // Not an int in my real code, but it doesn't really matter.
    int m_bar;
    boost::asio::io_service& m_io_service;
    boost::asio::strand m_bar_strand;
};

m_bar仅用于通过strand m_bar_strand调用的处理程序。这允许我不要在这些处理程序中锁定。

要从运行m_bar的线程外部设置io_service::run()属性,我写了一个asynchronous_setter,如下所示:

class Foo
{
  public:
    void async_get_bar(function<void (int)> handler)
    {
      m_bar_strand.post(bind(&Foo::do_get_bar, this, handler));
    }

    void async_set_bar(int value, function<void ()> handler)
    {
      m_bar_strand.post(bind(&Foo::do_set_bar, this, value, handler));
    }

  private:

    void do_get_bar(function<void (int)> handler)
    {
      // This is only called from within the m_bar_strand, so we are safe.

      // Run the handler to notify the caller.
      handler(m_bar);
    }

    void do_set_bar(int value, function<void ()> handler)
    {
      // This is only called from within the m_bar_strand, so we are safe.
      m_bar = value;

      // Run the handler to notify the caller.
      handler();
    }

    int m_bar;
    boost::asio::io_service& m_io_service;
    boost::asio::strand m_bar_strand;
};

这很有效但现在我想编写set_bar的同步版本来设置值并仅在集合生效时返回。它必须仍然保证有效集将在m_bar_strand内发生。理想情况下,某些东西是可重入的。

我可以想象具有信号量的解决方案可以在处理程序中进行修改,但我提出的所有内容似乎都是hackish而且并不优雅。 Boost / Boost Asio中有什么东西允许这样的东西吗?

您将如何继续实施此方法?

3 个答案:

答案 0 :(得分:6)

如果您需要同步等待设置值,那么Boost.Thread的futures可能会提供一个优雅的解决方案:

  

期货库提供了一种处理同步未来值的方法,无论这些值是由另一个线程生成,还是在单个线程上响应外部刺激,或按需生成。

简而言之,创建boost::promise并允许在其上设置值。稍后可以通过关联的boost::future检索该值。这是一个基本的例子:

boost::promise<int> promise;
boost::unique_future<int> future = promise.get_future();

// start asynchronous operation that will invoke future.set_value(42)
...

assert(future.get() == 42); // blocks until future has been set.

这种方法有两个显着的好处:

  • future是C ++ 11的一部分。
  • 例外情况甚至可以通过promise::set_exception()传递给future,支持向来电者提供例外或错误的优雅方式。

以下是基于原始代码的完整示例:

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>

class Foo
{
public:

  Foo(boost::asio::io_service& io_service)
    : m_io_service(io_service),
      m_bar_strand(io_service)
  {}

public:

  void async_get_bar(boost::function<void(int)> handler)
  {
    m_bar_strand.post(bind(&Foo::do_get_bar, this, handler));
  }

  void async_set_bar(int value, boost::function<void()> handler)
  {
    m_bar_strand.post(bind(&Foo::do_set_bar, this, value, handler));
  }

  int bar()
  {
    typedef boost::promise<int> promise_type;
    promise_type promise;

    // Pass the handler to async operation that will set the promise.
    void (promise_type::*setter)(const int&) = &promise_type::set_value;
    async_get_bar(boost::bind(setter, &promise, _1));

    // Synchronously wait for promise to be fulfilled.
    return promise.get_future().get();
  }

  void bar(int value)
  {
    typedef boost::promise<void> promise_type;
    promise_type promise;

    // Pass the handler to async operation that will set the promise.
    async_set_bar(value, boost::bind(&promise_type::set_value, &promise));

    // Synchronously wait for the future to finish.
    promise.get_future().wait();
  }

private:

  void do_get_bar(boost::function<void(int)> handler)
  {
    // This is only called from within the m_bar_strand, so we are safe.

    // Run the handler to notify the caller.
    handler(m_bar);
  }

  void do_set_bar(int value, boost::function<void()> handler)
  {
    // This is only called from within the m_bar_strand, so we are safe.
    m_bar = value;

    // Run the handler to notify the caller.
    handler();
  }

  int m_bar;
  boost::asio::io_service& m_io_service;
  boost::asio::strand m_bar_strand;
};

int main()
{
  boost::asio::io_service io_service;
  boost::asio::io_service::work work(io_service);
  boost::thread t(
      boost::bind(&boost::asio::io_service::run, boost::ref(io_service)));

  Foo foo(io_service);
  foo.bar(21);
  std::cout << "foo.bar is " << foo.bar() << std::endl;
  foo.bar(2 * foo.bar());
  std::cout << "foo.bar is " << foo.bar() << std::endl;

  io_service.stop();
  t.join();
}

提供以下输出:

foo.bar is 21
foo.bar is 42

答案 1 :(得分:0)

async_set_bar()中设置值时,可以使用管道通知同步方法。警告,下面的代码是大脑编译的,可能有错误,但它应该得到点

#include <boost/asio.hpp>

#include <iostream>
#include <thread>                                                                                                                       

class Foo                                                                                                                               
{
public:                                                                                                                                 
    Foo( boost::asio::io_service& io_service ) :                                                                                        
        _bar( 0 ),
        _io_service( io_service ),                                                                                                      
        _strand( _io_service ),                                                                                                         
        _readPipe( _io_service ),
        _writePipe( _io_service )
    {
        boost::asio::local::connect_pair( _readPipe, _writePipe );
    }

    void set_async( int v ) {
        _strand.post( [=]
            {
                _bar = v;
                std::cout << "sending " << _bar << std::endl; 
                _writePipe.send( boost::asio::buffer( &_bar, sizeof(_bar) ) );
            }
            );
    }

    void set_sync( int v ) {
        this->set_async( v );
        int value;
        _readPipe.receive( boost::asio::buffer(&value, sizeof(value) ) );
        std::cout << "set value to " << value << std::endl;
    }


private:
    int _bar;
    boost::asio::io_service& _io_service;
    boost::asio::io_service::strand _strand;
    boost::asio::local::stream_protocol::socket _readPipe;
    boost::asio::local::stream_protocol::socket _writePipe;
};

int
main()
{
    boost::asio::io_service io_service;
    boost::asio::io_service::work w(io_service);
    std::thread t( [&]{ io_service.run(); } );
    Foo f( io_service );
    f.set_sync( 20 );
    io_service.stop();
    t.join();
}

如果您不能使用c ++ 11 lambdas,请将其替换为boost::bind以及更多完成处理程序方法。

答案 2 :(得分:0)

这就是我提出的:

class synchronizer_base
{
    protected:
        synchronizer_base() :
            m_has_result(false),
            m_lock(m_mutex)
        {
        }

        void wait()
        {
            while (!m_has_result)
            {
                m_condition.wait(m_lock);
            }
        }

        void notify_result()
        {
            m_has_result = true;
            m_condition.notify_all();
        }

    private:

        boost::atomic<bool> m_has_result;
        boost::mutex m_mutex;
        boost::unique_lock<boost::mutex> m_lock;
        boost::condition_variable m_condition;
};

template <typename ResultType = void>
class synchronizer : public synchronizer_base
{
    public:

        void operator()(const ResultType& result)
        {
            m_result = result;

            notify_result();
        }

        ResultType wait_result()
        {
            wait();

            return m_result;
        }

    private:

        ResultType m_result;
};

template <>
class synchronizer<void> : public synchronizer_base
{
    public:

        void operator()()
        {
            notify_result();
        }

        void wait_result()
        {
            wait();
        }
};

我可以这样使用它:

class Foo
{
  public:
    void async_get_bar(function<void (int)> handler)
    {
      m_bar_strand.post(bind(&Foo::do_get_bar, this, value, handler));
    }

    void async_set_bar(int value, function<void ()> handler)
    {
      m_bar_strand.post(bind(&Foo::do_set_bar, this, value, handler));
    }

    int get_bar()
    {
      synchronizer<int> sync;

      async_get_bar(boost::ref(sync));

      return sync.wait_result();
    }

    void set_bar(int value)
    {
      synchronizer<void> sync;

      async_set_bar(value, boost::ref(sync));

      sync.wait_result();
    }
};

boost::ref是必需的,因为synchronizer的实例是不可复制的。这可以通过将synchronizer包装在其他容器类中来避免,但我对该解决方案没问题。

注意: NOT 从处理程序内部调用此类“同步”函数,否则它可能会死锁!