
Wow, what a thread. Rather than replying to everything individually, I'm going to try to address the issues in this one email. --- Arkadiy Vertleyb <vertleyb@hotmail.com> wrote:
My problem with the current approach is that synchronous operations seems to be viewed as second-class citizens.
On the contrary, asio attempts to elevate asynchronous operations to the same level as their synchronous counterparts. For virtually every blocking synchronous operation in asio, there is an asynchronous equivalent. The intent is to improve ease of use for asynchronous operations to be as close as possible to synchronous. Far from synchronous operations being given second class status, considerable effort has gone into ensuring that the composed operations (such as asio::[async_]read and asio::[async_]write) have the same semantics for both synchronous and asynchronous calls. Further levels of abstraction should also be achievable in a similar vein. Let me state as clearly as possible: the synchronous operations are not implemented in terms of the asynchronous operations. They call the underlying synchronous system call with little overhead. The question was raised over whether a synchronous-only sockets API should be provided, and asio implemented in terms of that. I rejected this approach early on in the development of asio. As others have already noted, such an API is not sufficient for the development of asynchronous operations, and you are forced to step outside of it to use operating system-specific functionality. Instead, asio is itself intended as a thin, portable API that provides both synchronous and asynchronous support. So, the nub of your issue appears to be the conceptual difficulties you perceive in having the socket constructor take a demuxer, and for the interface to include both synchronous and asynchronous operations. Let's consider the alternative approach proposed: to split the socket class into separate sync_socket and async_socket classes. The drawbacks of this approach are increased redundancy and complexity in the programming interface. If this approach were already in place, I can imagine that I would be getting questions about why there are so many socket classes, and which one is the most appropriate to use. There are trade-offs in every design decision. I elected to give synchronous and asynchronous operations equal weight, and combine them in a single interface. This has the added advantage of allowing mixed-mode programming, where synchronous operations can be used alongside asynchronous in a single application. As you point out, there are situations where synchronous is more appropriate, and these differences can occur within an application. I don't think that the need to pass a demuxer to the socket constructor is conceptually difficult to explain. It's there for the asynchronous operations, if and when you need them. I also do not agree that it is onerous to have to pass the demuxer to the constructor, or to pass it wherever it's needed in the program. You can make it a global, declare a new one for each use, or even use one associated with an existing object, as in: stream_socket s(my_acceptor.demuxer()); Therefore I stand by my design choices as the best balance between usability, functionality and flexibility. Now, to turn to more philosophical matters, namely synchronous versus asynchronous approaches. As you pointed out, synchronous socket programming is easier to debug. However, there are also disadvantages, notably increased resource usage (with a thread required per connection) reducing scalability, and the need for explicit synchronisation between threads. Correct programming in the presence of threads is a difficult art, and I see this as a major disadvantage of the synchronous approach. Asynchronous socket programming, on the other hand, has the advantage of allowing concurrent operations without synchronisation. Programs can be written as though they exist in a single-threaded environment, and still scale to handle numerous connections. The main disadvantage of the asynchronous approach is program complexity, since operation initiation is separate from completion, and the flow of control is inverted. However I have designed asio to mitigate these as much as possible, particularly in providing the ability to combine asynchronous operations to allow the implementation of higher-level abstractions. When people first start network programming, it is natural for them to gravitate towards synchronous operations. They may then progress to writing more complex applications, such as servers, that must handle multiple connections. If all they have been exposed to is synchronous network programming, then it is likely they will end up with a design that requires many threads. By making both synchronous and asynchronous operations readily available, asio may inform programmers that there are alternatives to multithreading. If this helps them to make a more appropriate choice for their application, then that has got to be a good thing. Cheers, Chris