Boost logo

Boost :

From: John Bytheway (jbytheway+boost_at_[hidden])
Date: 2008-08-17 16:43:46


I had a similar problem to this recently when writing a program that
used asio and GTK (under Linux), and I thought I'd share my solution
here in case it's helpful (it's one you didn't suggest).

dizzy wrote:
> On Thursday 07 August 2008 11:52:37 Felipe Magno de Almeida wrote:
<snip>
>> That won't work. I have to wait for events on the thread that run is
>> running in, which
>> is the user thread and is the same that creates the windows.
>
> Is this a limitation of the GUI framework? You only need to _wait_ for the
> events in this internal thread, the actual dispatching still happens through
> normal asio mechanisms to the threads that execute run(). If your GUI somehow
> assigns some kind of ownership of the window creator thread so only that
> thread can wait/query for events then this is a serious limitation because it
> won't allow you to easily in the future have multiple threads execute run()
> for scalability reasons.

The problem I was having was that I have one thread running
Gtk::Main::run, which does all the GUI event handling, and another
thread doing io_service::run(). This broke when anything in the latter
thread tried to change the GUI (e.g. to display a message to the user)
it couldn't alter the GUI (doing so would cause crashes somewhat at
random, presumably because the data structures in question were not
locking). Possibly some sort of locking is possible, but a more obvious
solution is to require all GUI updates to be done on the GUI thread.

> You won't be able to _really_ wait events in the same thread since that
> already blocks using some OS dependent I/O notification mechanism (say it
> blocks in select()) which may not permit notification of GUI events anyways
> (not to mention this does not seem to be a configurable part of asio without
> depending on "detail" stuff). A workaround is to get all events be some sort
> of I/O events (ex using socketpair) but then you need the GUI to use the
> socketpair or you need that separate thread to wait for GUI events and post to
> the socketpair (but then the socketpair is not needed since you can directly
> post() events on the io_service).

I don't see a way of coercing the GUI events to be of this form, at
least not in a way useful for asio.

> Another general solution but that has its own shortcomings is to have your own
> event loop that calls run_one() and on each iteration you "peek" into the
> other event source (the GUI event source you want to integrate with asio).

This was roughly my first solution. As you say...

> This works fine if asio is busy but if not you can also program a timer with
> asio so it will make run_one() return at least once per the time configured in
> the timer. It obviously has the problem that if asio is not busy GUI events
> will be handled at this timer granularity.

I tried the more naive thing of polling both asio and the GUI with
sleeps interspersed, but I couldn't find a sleep that was sufficiently
brief (e.g. usleep(0) sleeps for ~10ms, which is too long to get a
responsive GUI); would your solution allow a sufficiently fine granularity?

> If GUI events are more important
> than asio generated ones then you could do the reverse (make an event loop
> that calls some sort of run_one() function for the GUI events, a function that
> waits for at least an event and dispatches it and returns and then you use
> io_service.peek() to execute possible asio queued events).

That should work. It would require a way to ensure that suitably marked
posts are executed only on the GUI thread; presumably that's possible
with something in asio (strands?).

My solution is roughly the reverse of your first suggestion: coerce all
events to be GUI events and use GTK's run() function only on the GUI
thread. This is the 'official' GTK solution, and uses the
Glib::Dispatcher class (which I found only after much searching).
Glib::Dispatcher works much like boost::signal<void ()>, but the
function is called asynchronously on the GUI thread.

I used Glib::Dispatcher to build a more general version of itself
(called CrossThreadSignal) that takes arguments. I've included the
source below for completeness (requires C++0x). I use this for example
as a way of posting messages to the GUI with something like

   // In GUI thread

   CrossThreadSignal<std::string> messageSignal;
   messageSignal.connect(sigc::ptr_fun(&writeMessage));

   // Then in another thread

   messageSignal("message");

I suspect the implementation can be improved; in particular it feels
like it should be possible to use a lock-free data structure of some
kind where I'm using a std::list and a mutex.

John

// Signal which when called on one thread will be raised
// asynchronously on the GUI thread (and thus can safely mess with
// widgets)
template<typename... Args>
class CrossThreadSignal {
   public:
     CrossThreadSignal() {
       callAlert_.connect(
           sigc::mem_fun(this, &CrossThreadSignal::flush)
         );
     }

     template<typename Function>
     void connect(const Function& f) {
       signal_.connect(f);
     }

     void operator()(const Args&... args) {
       {
         boost::lock_guard<boost::mutex> lock(pendingCallsMutex_);
         pendingCalls_.push_back(
             boost::bind(&CrossThreadSignal::raise, this, args...)
           );
       }
       callAlert_();
     }
   private:
     void raise(const Args&... args) {
       signal_(args...);
     }

     typedef decltype(
         boost::bind(
           &CrossThreadSignal::raise,
           ((CrossThreadSignal*)0), *((Args*) 0)...
         )
       ) PendingCall;
     // Each list entry is a set of arguments
     std::list<PendingCall> pendingCalls_;
     boost::mutex pendingCallsMutex_; // Mutex for above list
     // The trigger that can signal between threads
     Glib::Dispatcher callAlert_;
     // Signal to connect to; raised on GUI thread
     boost::signal<void (const Args&...)> signal_;

     void flush() {
       boost::lock_guard<boost::mutex> lock(pendingCallsMutex_);
       while (!pendingCalls_.empty()) {
         pendingCalls_.front()();
         pendingCalls_.pop_front();
       }
     }
};


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk