bloomberg / ntf-core

Sockets, timers, resolvers, events, reactors, proactors, and thread pools for asynchronous network programming
Apache License 2.0
71 stars 23 forks source link

Callback factories #23

Open michihenning opened 1 year ago

michihenning commented 1 year ago

Why would I use a callback factory when setting up an async call? I can do this:

auto callback = socket_->createReceiveCallback([this](auto receiver, auto blob, auto event) {
    received_identity(receiver, blob, event);
});
auto error = socket_->receive(r_opts, callback);

This works fine. But I can also do this:

auto error = socket_->receive(r_opts, [this](auto receiver, auto blob, auto event) {
    received_identity(receiver, blob, event);
});

Whether I use the callback from the factory or pass a lambda directly makes no apparent difference. Why would I use one over the other?

mattrm456 commented 1 year ago

The difference is described in the section titled "Asynchronous Operation" in the ntci::StreamSocket class documentation. The result of createXyxCallback associates a function with an arbitrary strand and an optional mechanism to prevent the function from being called when the operation completes. Some users may never care about such strands and authorization; the overloads that accept a lamba are provided for convenience.

michihenning commented 1 year ago

Thank you for that, I missed this.

michihenning commented 1 year ago

What the documentation does not make clear is that, if there is only one thread in an interface, the strand is implicit and all the callbacks will be invoked on that single thread. (At least, that's what my experimentation suggests.) Any hoops I might jump through with callback factories and strands are wasted (and possibly wasteful as well?) if there is only one thread in the interface.

It might be nice to say a little bit more about what strands are, how they work, and how they relate to the concurrency (or otherwise) of callbacks sharing the same strand or using different strands. Does a strand guarantee that all callbacks with that strand will be invoked sequentially? Does it guarantee that all callbacks with that strand will be invoked by the same thread? Does a strand guarantee that, if I invoke async operation A followed by operation B, the completion handler for A will be invoked before B, even if B finishes before A? These are all questions in the reader's mind.

As it stands, the doc pretty much assumes that the reader knows what a strand is. I suspect that quite a few readers will know only vaguely.