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.
Summary¶
class qi::Actor
Functions (class qi::Actor)
class qi::Strand
Global Classes
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¶
Public Functions¶
-
()Strand
-
(qi::ExecutionContext& executionContext)Strand
-
()~Strand
-
void
()join
-
OptionalErrorMessage
(std::nothrow_t)join
-
qi::Future<void>
(const boost::function<void()>& cb, qi::SteadyClockTimePoint tp)async
-
qi::Future<void>
(const boost::function<void()>& cb, qi::Duration delay)async
-
bool
() constisInThisContext
- template<typename F>
-
auto
(F&& func, boost::function<void()> onFail, ExecutionOptions options)schedulerFor
- template<typename F>
-
auto
(F&& func, boost::function<void()> onFail, ExecutionOptions options)unwrappedSchedulerFor
-
Future<void>
(const boost::function<void()>& cb, MicroSeconds delay)defer
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. : Under extreme circumstances such as system memory exhaustion, this method could still throw a std::bad_alloc exception, thus causing a call to std::terminate because of the noexcept specifier. This behavior is considered acceptable.
-
OptionalErrorMessage
qi::Strand::
join
(std::nothrow_t)¶ Joins the strand.
DeprecatedUse join() which is currently noexcept.
This version catches any exception and returns its message. This version must be preferred in destructors to prevent abort.
If there is no exception, an empty error message is returned.
Example: Preventing a destructor to throw exceptions because of join. ~MyActor(){ if(constautoerror=_strand.join(std::nothrow)){ qiLogWarning()<<”Errorwhilejoiningthestrand.Detail:”<<*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
()const
¶ 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 = {}, ExecutionOptions options = defaultExecutionOptions()¶
- template<typename F>
-
auto
qi::Strand::
unwrappedSchedulerFor
(F&& func, boost::function<void()> onFail = {}, ExecutionOptions options = defaultExecutionOptions()¶ Returns a function which, when called, defers a call to the original function to the strand, but with the return value unwrapped if possible.
-
Future<void>
qi::Strand::
defer
(const boost::function<void()>& cb, MicroSeconds delay = MicroSeconds::zero()¶ Brief:
Parameters: - cb – The function to execute.
- delay – Duration that defer will wait (without blocking the caller) before queuing the function for execution. If zero (the default value), the function will be queued immediately.
Returns: A future that is set once the function argument is executed
Defers a function for execution in the strand thus without allowing the strand to call it from inside this function. It implies that this function always returns immediately.
qi::Actor Class Reference¶
Public Functions¶
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.