<snip>
Just out of curiosity, I implemented your example just with executors and future::then: https://gist.github.com/sithhell/42eec1e183df206556086cfbf20918ac
Oh we are back to HPX... Well, if it's out of curiosity... I can't promise to give a correct answer due to my little knowledge of HPX. Please excuse my possibly incorrect interpretations.
Granted, it doesn't bind the lifetime of the manager to the object, but that should be a trivial, for example with an intrusive pointer instead of raw this in the continuations. And it misses the proxy object which could be implemented to hide this lifetime tracking in a similar way that boost.async, although, the destructor of the executor waits until all threads have been processed (implicitly keeping the object alive long enough). Other than that, it has the same race freedom guarantees, due to only ever manipulating the object on a single thread of execution. It only blocks on the future returned from start(). The location on where to execute the callback/ continuation is fixed. The "thread world" is defined by the executor, which is a very generic and powerful abstraction and properly aligned with the current development in the C++ Standards Committee. The layered example can be done in a similar fashion, each object containing its own executor. What do you think?
From what I understand, I don't think both examples are equivalent (yet): - the lifetime issues are not visible in this example. There is a single object and it is living in main's thread, which removes quite some salt. It has no interaction whatsoever with other threads or objects. My example shows interaction with the main thread. - as you wrote, there is no proxy. If the manager object lived in another thread than main, it would be non-trivial to avoid races. Calls to members would be a race. Destructor would also become an issue. A key point of my example is that the object is now complete and reusable in a different thread context. I could extend the example at will with more threads and continue to have no thread issue. As it is, your manager object is still thread-unsafe if used in another context. Of course it can be implemented, it is already done, in servant_proxy. - how many threads are there? I'm unsure if one or two. In my example, there are 3 (main, servant thread, threadpool), though I'm sure your example can be extended to have the same number. It looks like task and callback are executed in the same thread (executor). Correct? My example executed the long tasks in the threadpool. I'm quite sure your example can also be extended to do the same. I think it's great. We are just scratching the surface but we are coming to the interesting stuff. In the example, the manager object is created on the stack, is alone with no interaction with other objects or libraries, even less with external threads. But if we imagine lots of thread worlds, each with hundreds of servants and proxies, things start becoming interesting. One simply cannot define all of the application objects in main and life issues appear quickly. I'll try to think of a realistic example showing such a thing. You might want to have a look at the next example in the doc, the layers, which makes things even more interesting by introducing safe callbacks, which can be called from any thread (asynchronous thread worlds or whatever thread the application has). I hope it will then be clear that a single object on a stack is not a general solution and that an object living and being accessed in a clearly defined thread with no outside possibility to use it incorrectly is a better one. Sorry, it looks like generating the doc broke the links. Here the link to the named examples: https://htmlpreview.github.io/?https://github.com/henry-ch/asynchronous/blob... Regards, Christophe