
Hi All, I've renamed the subject from "Asynchronous I/O" because I wanted to float the notion that async behavior not be viewed as an aspect of I/O, though it is highly desired in that space. Below is a sketch and some pseudo-code of how I've done this over the past few years, in a portable manner. The trick to portability for me was integrating into various kinds of threads or main loops. For example: the main GUI thread of a Windows application, or a Motif (Xt) based application, or a Mac application's main event loop. These are the hard ones. Once they are possible, most (more likely "all") others are possible as well. Unfortunately, the needs of these integrators is not portable, but can be hidden behind simple wrapper classes for anticipated cases. To handle other cases, the integration mechanism needs to be visible. In the case of the GUI thread integrations, I have a single class to instantiate, though its details were wildly different for each platform. Depending on how this discussion goes, I plan to convert these ideas into a proposed library. Best, Don ------ Sketch -------- 1. The user creates one or more "nexus" objects. These objects store the queue of events, typically one-per-thread. Since some action is typically required as messages are enqueued, a callback is supplied to open() which gets called as each message is added. 2. The owner of the nexus is responsible for calling the pump() method to drain the messages. This method is intended for only one thread and hence is not internally thread-safe (i.e., BYOM ... Bring Your Own Mutex). 3. To queue calls, client code needs to create a "channel" object and connect it with a nexus. The reason for this object is that it is a proxy for the "ability to still dispatch a message". In other words, if a receiver object finds itself in its destructor, it will want to cancel any queued calls to it. Of course, this can be applied externally to objects, which is why channel is not a base class. 4. Once a channel is open, the async_call() method is used to put a message in flight. This is thread-safe. 5. Where other code needs a function<>, the channel can be used to create a shim function<> that accepts N arguments, and binds them into a queued call. ------ Pseudo-code -------- class nexus { friend class channel; public: typedef function<void (void)> message; // Return true if there is an object associated with // the current thread. static bool available (); // Return the object associated with the current // thread. Throw if there is none. static nexus & get_current (); // Connect this object with the current thread. The // ntfy callback is made whenever a message is added // to this queue. void open (const message & ntfy); void close (); // Remove a message from the queue. Don't wait any // longer than "t". If t == 0 and there is a message // in the queue, dispatch it now. void pump (const timeout & t = timeout::infinite); private: struct entry { channel * ch; message msg; }; typedef std::list<entry> messages; messages msgs_; message ntfy_; void enqueue (channel * ch, const message & msg) { msgs_.push_back(entry(ch, msg)); ntfy_(); } // Remove all entry's in msgs_ with == channel*. void purge (channel * ch); }; class channel { typedef nexus::message message; public: void open (nexus * nx = 0) { if (!nx) nx = &nexus::get_current(); close(); target_ = nx; } void close () { target_->purge(this); } void async_call (const message & msg) { target_->enqueue(this, msg); } // Create a callback that calls "this->async_call(msg)". message bind_async_call (const message & msg); // Create a callback that accepts T and calls // "this->async_call(bind(msg, t))". template <typename T1> function<void (T)> bind_async_call (const function<void (T)> & msg); // More flavors of bind_async_call() private: nexus * target_; }; __________________________________ Do you Yahoo!? Yahoo! Small Business - Try our new resources site! http://smallbusiness.yahoo.com/resources/

Don G wrote:
Hi All,
I've renamed the subject from "Asynchronous I/O" because I wanted to float the notion that async behavior not be viewed as an aspect of I/O, though it is highly desired in that space.
Hi Don, sorry for my late answer but work caught me again. At least it's good to see others jumping in and keeping the discussion alive. :)
[...] ------ Sketch --------
1. The user creates one or more "nexus" objects. These objects store the queue of events, typically one-per-thread. Since some action is typically required as messages are enqueued, a callback is supplied to open() which gets called as each message is added.
It reminds me of I/O completion ports - several ports each of them handling several events? Is your idea similar to this?
2. The owner of the nexus is responsible for calling the pump() method to drain the messages. This method is intended for only one thread and hence is not internally thread-safe (i.e., BYOM ... Bring Your Own Mutex).
3. To queue calls, client code needs to create a "channel" object and connect it with a nexus. The reason for this object is that it is a proxy for the "ability to still dispatch a message". In other words, if a receiver object finds itself in its destructor, it will want to cancel any queued calls to it. Of course, this can be applied externally to objects, which is why channel is not a base class.
4. Once a channel is open, the async_call() method is used to put a message in flight. This is thread-safe.
5. Where other code needs a function<>, the channel can be used to create a shim function<> that accepts N arguments, and binds them into a queued call.
------ Pseudo-code --------
class nexus
The concept of the class really reminds me of a I/O completion port. The only difference is that there is no invisible background thread being responsible for a nexus object - the application has to make sure that there is one thread controlling a nexus object?
[...] class channel
AFAICS you use this class to communicate with a nexus object. The owner thread does the multiplexing (waiting for different events in a nexus object) and other threads communicate with this nexus object through channels which are responsible for synchronizing? Is this correct? So the difference between your approach and I/O completion ports in Windows is ... hm, I think in Windows a completion port is a combination of your nexus and channel, isn't it? Boris
[...]
participants (2)
-
Boris
-
Don G