2

Is it safe (or reasonable) for a the callback from asio to block?

void Connection::start() {
    /* Assume std::string buffer; that has data to write. */
    auto callbackFn = boost::bind(&Connection::callback, shared_from_this(),
                                  boost::asio::placeholders::error,
                                  boost::asio::placeholders::bytes_transferred);

  boost::asio::async_write(_socket, boost::asio::buffer(buffer), callBackFn);
}

void Connection::callback(const boost::system::error_code& error,
                              std::size_t bytesSent) {
    /* Perform long running action with bytes sent */
    boost::asio::async_write(...);
}

Or should I spawn a thread in callback, then return immediately and do operations when that thread is done?

Braaedy
  • 511
  • 4
  • 13

1 Answers1

3

Yes, it is reasonable in many scenarios. But you as the architect are responsible for deciding if it is actually reasonable in your scenario.

Generally speaking, your io_service will run on one thread, and that is the thread it will invoke your callback on, which means it is not processing messages while it is invoking the callback. If that's okay, then you're good to go. (Note that the "safe" part comes up here -- are you properly protecting your objects for use across threads?)

If it's not okay, then you should ship off the message to another thread for processing.

As for me and my house, we prefer the Active Object pattern to keep the IO service responsive and the multithreading worries to minimum. See Herb Sutter's article series on Effective Concurrency. Specifically, you should read:

PS, If at all possible, drop boost::bind() in favor of C++11/14 lambdas.


Update: As for why to prefer lambdas, the answer boils down to clarity and maintainability (performance is about the same, with a slight edge for lambdas). Once you are used to the lambda syntax for capture and what not, it is much more natural. Bind requires reading it "inside out", never becomes natural to parse mentally, and is even more tedious for member functions and capturing references. Consider (adapted from the Boost docs):

struct S
{
    void foo( int&, double ) const;
} s;

// ...
auto x      = 0;
auto binder = bind( &S::foo, s, ref(x), placeholder::_1 ); // s.foo(x-as-ref, _1)
auto lambda = [&]( auto d ) { s.foo( x, d ); };

auto d = 42.0;
binder( d );
lambda( d );

The call syntax at the bottom is same, but the lambda definition is much clearer as to what's actually happening. Lambdas also easily expand to multiple statements, whereas bind does not.

metal
  • 5,609
  • 30
  • 44
  • This is exatly what I was looking for, thank you! I needed to know that it was just handling events on a single thread. I'll need to return to my project since it's in the early stages to determine if it's ok, but I at least know how that's working. I'll look into that article as well thank you. As for lambdas, I took much of my architecture from examples and I'll be going through and changing things to fit more stuff. As a secondary question, why lambda over std::bind? – Braaedy Aug 11 '17 at 20:58
  • 1
    @Braaedy Re handlers: it is also possible to call `io_service::run()` multiple times on separate threads to have multiple parallel handlers. See this helpful section on "[Scalability and Multithreading](https://theboostcpplibraries.com/boost.asio-scalability)" in the Boost.ASIO section of Boris Schäling's _The Boost C++ Libraries_. – metal Aug 14 '17 at 12:23
  • 1
    @Braaedy Re lamdas: I've added an update. See also [this Q&A](https://stackoverflow.com/a/17545183/201787). – metal Aug 14 '17 at 13:21
  • Ah thanks for the update. I've spent the last weekend immersing myself in all of this so already lambda has gotten pretty natural. I've looked into multithreading and have designed with it in mind, but I'm not quite there yet. Thanks again! – Braaedy Aug 14 '17 at 15:35