Boost logo

Boost :

From: Douglas Gregor (gregod_at_[hidden])
Date: 2002-02-26 22:48:13


On Tuesday 26 February 2002 08:08 pm, you wrote:
> Just out of curiousity, have you thought about
> asynchronous signals/callbacks, in particular across
> threads? That has been my general need, and I
> recently finalised a solution that works reasonably
> well. It is based on the observer pattern, in that
> there is a template ABC 'observer' with an on_notify
> function that receives events which are of any type
> whatsoever. You can post any type of event from
> anywhere, and if there are any currently registered
> observers, they will pick it up next time you return
> to the event processing loop. Because it is always
> asynchronous, it's perfectly safe to have the observer
> baseclass self-register, even though 'this' isn't
> fully initalised.
>
[snip description]
>
> Anyway, just thought I should bring this up.
> Obviously the design is fairly fundamental different
> to the proposed signal/slot library, but some of the
> concepts might be useful.
>
> Dylan

To answer your question: I've thought about it, but not seriously enough yet
to have formed any solid opinions regarding an appropriate interface.

I'm not quite sure the difference in design is fundamental. Looking at your
'observer' class, it looks like what the Signals library calls a 'slot' -
i.e., it is an entry point to receive messages. post_event then looks like a
'signal' (with a single target, so it is more like a callback) in the
terminology of the Signals library. If we ignore the fact that a signal is
unsafe to use from multiple threads, here's a way we could map the
synchronous slot calls used in the Signals library into asychronous calls
across threads:

// Picks up events for this thread and execute them
class event_processor {
  std::queue<boost::function0<void> > events;
  some_mutex mutex;

public:
  // Add an event into the queue
  void add_event(const boost::function0<void>& func)
  {
    some_mutex::lock lock(mutex);
    events.push(func);
  }

  // Process a single event on the queue, if there is one
  void process_event()
  {
    some_mutex::lock lock(mutex);
    if (!events.empty()) {
      events.front()(); // execute the event
      events.pop();
    }
  }
};

// Post an invocation to a an event_processor
template<typename Functor>
class post_message_t {
  event_processor& ep;
  Functor functor;

public:
  post_message_t(event_processor& e, Functor f) : ep(e), functor(f) {}

  // Invocation of this function object. There would be lots of copies of
  // this routine for different values of N
  template<typename T1, typename T2, ..., typename TN>
  void operator()(const T1& a1, const T2& a2, ..., const TN& aN) const
  {
    ep.add_event(boost::bind<void>(functor, a1, a2, ..., aN));
  }
  
  template<typename Visitor>
  void visit(Visitor& v)
  {
    visit_each(functor, v, 0);
  }
};

// Make post_message_t objects place nice with Signals
template<typename Visitor, typename Functor>
void visit_each(const post_message_t<Functor>& f, Visitor& v, long)
{
  f.visit(v);
}

// Helper to construct post_message_t objects
template<typename Functor>
inline post_message_t<Functor>
post_message(event_processor& e, Functor f)
{
  return post_message_t<Functor>(e, f);
}

Now I'll modify your code to show the intent of the above code:

void print_args {
  void operator()(int x, const std::string& s) const
  {
    std::cout << x << ": " << s << std::endl;
  }
};

void background_thread(event_processor& ep)
{
  signal2<void, int, std::string> sig;
  sig.connect(post_message(ep, print_args()));
  for (;;)
  {
    /* ... * /
    sig(5, "foo");
    /* ... * /
  }
}

int main()
{
  event_processor ep;
  start_thread(boost::bind(&background_thread, boost::ref(ep));
  for (;;)
     sob.process_event();
}

Now for some explanation: the 'event_processor' class is similar to your
event_processor: it just picks events off the queue and runs them in the
current thread. The post_message_t object takes a call in one thread,
packages it up in a function object, and then posts that function object in
the event queue referenced by the post_message_t object.

Note that post_message_t can turn any synchronous callback into an
asychronous callback, so it could be used with a signal-target callback like
Boost.Function or be used on a per-slot basis with one of the Signals library
signal classes.

The code I give will work, but note that neither Boost.Function nor the
Signals library are thread-safe, so any modifications performed across thread
barriers will be bad news. I will address this in the future.

        Doug


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