qi::Strand and qi::Actor

A Strand allows one to schedule asynchronous work with the guarantee that two tasks will never be executed in parallel. This is useful to avoid data races, mutexes, and deadlocks.

An Actor receives messages and processes them. Basically, it’s just an object containing a Strand so that all asynchronous work it receives will be executed sequentially.

Detailed Description

Strand Usage

A Strand is an execution context, just like the so-called “event loop”:


             
              #include <qi/strand.hpp>

qi::Strand strand;
auto future = strand.asyncDelay(myFunction, qi::MilliSeconds(100));

// you can now cancel future
future.cancel();

// and/or wait for it
future.wait();

             
            

A strand’s destructor will block until all work scheduled is finished or canceled. You must not schedule any work when the strand is being destroyed.

A strand accepts a parent execution context at construction, on which it relies to execute the tasks. If no execution context is provided, the global “event loop” is used. Beware that the execution context must not be destroyed before all Strands relying on it are destroyed.

Sometimes you may need to share an object that relies on a Strand . But external code cannot know this implementation detail, and you cannot prevent direct calls to your object’s methods in C++. It is therefore recommended to schedule in the Strand all the calls to your object’s public interface. To do so, the following code pattern is to be used in every method definition:


             
              MyObject::myPublicMethod(const std::string& myArg)
{
  _strand.async([myArg]
  {
    // ... your code here
  });
}

             
            

For example, if you have the following stack implementation:


             
              class UnsafeIntStack
{
  std::vector<int> _vec;

public:
  void push(int v) { _vec.push_back(v); }
  int pop()
  {
    if _vec.empty() throw std::exception{};
    int ret = _vec.back();
    _vec.pop_back();
    return ret;
  }
};

             
            

You can make it thread-safe without using a mutex, with the help of a Strand :


             
              class StrandedIntStack
{
  std::vector<int> _vec;
  qi::Strand _strand;

public:
  qi::Future<void> push(int v)
  {
    _strand.async([this, v]{ _vec.push_back(v); });
  }

  qi::Future<int> pop()
  {
    return strand.async([this]
    {
      if _vec.empty() throw std::exception{};
      int ret = _vec.back();
      _vec.pop_back();
      return ret;
    });
  }
};

             
            

If your object is not meant to be called directly, but instead is made to be called remotely using libqi, it may be easier to use an Actor rather than scheduling the calls manually.

Actor Usage

To make a single-threaded object, you can inherit from Actor , which is a helper class wrapping a Strand . Actors are recognized by libqi so that all RPC calls to them will be scheduled in their Strand . Consequently, it is guaranteed that there will never be more than one RPC call in parallel on the object. Also, no more RPC call can be processed after the Actor is destroyed.

Warning

Actors are only recommended for stand-alone objects exposed to remote clients only. If you want several objects to share a Strand or if the object interacts with local code (running in the same process), you should not use Actor !


             
              #include <qi/actor.hpp>

class MyActor : public Actor
{
public:
  ~MyActor()
  {
    strand()->join();
  }

  void myFunction(const std::string& str);
};

// works with type-erasure
QI_REGISTER_OBJECT(MyActor, myFunction);
// works with futures
future.connect(qi::bind(&MyActor::myFunction, myActor, "my string"));
// works with signals
signal.connect(qi::bind(&MyActor::myFunction, myActor, _1));
// works with type-erased signal/property connections
myObject.connect("mySignal",
    boost::function<void(std::string)>(
      // you need the MetaCallType_Direct to have ordering guarantees
      qi::bind(&MyActor::myFunction, myActor, _1)), MetaCallType_Direct);
// works with periodic tasks
periodicTask.setCallback(qi::bind(&MyActor::myFunction, myActor, _1));
// works with async calls
qi::asyncDelay(qi::bind(&MyActor::myFunction, myActor, "my string"),
    qi::Seconds(1));

             
            

To control better the scheduling of RPC calls, it is recommended to call strand()->join() at the beginning of your destructor. Joining the Strand guarantees there is no more work scheduled on the Actor .

Warning

It is not possible to inherit both from Actor and Trackable because an Actor has the same behavior as Trackable.

Warning

If you call directly an object inheriting from qi::Actor, the call is not scheduled and therefore may be performed concurrently to other calls! Joining the Strand does not protect from performing direct calls!


              
               // *NOT* single threaded
myActor.myFunction("my string");

              
             

When connecting a callback of your object to a signal, do not use boost::bind or a lambda. For example, this code does not provide single-threading guarantees:


              
               // *NOT* single threaded
future.connect(boost::bind(&MyActor::myFunction, myActor, "my string"));

              
             

Same thing when scheduling directly on the main eventloop.


              
               // *NOT* single threaded
qi::getEventLoop()->async(boost::bind(&MyActor::myFunction, myActor,
    "my string"));

              
             

To have your calls scheduled on the strand, use qi::bind or explicitly strand your callback by using qi::Actor::stranded .

Reference

qi::Strand Class Reference

Introduction

More...


              
               #include <qi/strand.hpp>

              
             
  • Inherits: noncopyable , qi::ExecutionContext

Public Functions

Strand ( )
Strand ( qi::ExecutionContext& executionContext )
~Strand ( )
void join ( )
qi::Future<void> async ( const boost::function<void()>& cb , qi::SteadyClockTimePoint tp )
qi::Future<void> async ( const boost::function<void()>& cb , qi::Duration delay )
bool isInThisContext ( )
template<typename F>
auto schedulerFor ( F&& func , boost::function<void()> onFail )

Detailed Description

Class that schedules tasks sequentially

A strand allows one to schedule work on an eventloop with the guaranty that two callback will never be called concurrently.

Methods are thread-safe except for destructor which must never be called concurrently.

Function Documentation

qi::Strand:: Strand ( )

Construct a strand that will schedule work on the default event loop.

qi::Strand:: Strand ( qi::ExecutionContext& executionContext )

Construct a strand that will schedule work on executionContext.

qi::Strand:: ~Strand ( )

Call detroy()

void qi::Strand:: join ( )

Joins the strand

This will wait for currently running tasks to finish and will drop all tasks scheduled from the moment of the call on. A strand can’t be reused after it has been join()ed.

It is safe to call this method concurrently with other methods. All the returned futures will be set to error.

qi::Future<void> qi::Strand:: async ( const boost::function<void()>& cb , qi::SteadyClockTimePoint tp )

call a callback asynchronously to be executed on tp Deprecatedsince 2.5

qi::Future<void> qi::Strand:: async ( const boost::function<void()>& cb , qi::Duration delay )

call a callback asynchronously to be executed in delay Deprecatedsince 2.5

bool qi::Strand:: isInThisContext ( )

Brief:

Returns: true if current code is running in this strand, false otherwise. If the strand is dying (destroy() has been called, returns false)
template<typename F>
auto qi::Strand:: schedulerFor ( F&& func , boost::function<void()> onFail = {} )

qi::Actor Class Reference

Introduction

More...


              
               #include <qi/actor.hpp>

              
             
  • Inherited by: qi::SignalSpy

Public Functions

( )
( )
Actor ( qi::ExecutionContext& ec )
( )
qi::Strand* strand ( ) const
template<class... Args>
( )
template<class... Args>
( )
template<class... Args>
( )
template<class... Args>
( )
void joinTasks ( )

Detailed Description

Class that represents an actor.

Inherit from this class if you want your class to be an actor (as in the actor model). This means that your class will receive “messages” and not be called. In other words, there will never be to calls to your object in parallel, they will be queued.

Function Documentation

()
()
qi::Actor:: Actor ( qi::ExecutionContext& ec )
()
qi::Strand* qi::Actor:: strand ( ) const
()
()
()
()
void qi::Actor:: joinTasks ( )