c++-gtk-utils
Public Member Functions | List of all members
Cgu::AsyncResult< T > Class Template Reference

A thread-safe asynchronous result class. More...

#include <c++-gtk-utils/async_result.h>

Public Member Functions

 AsyncResult ()
 
 ~AsyncResult ()
 
 AsyncResult (const AsyncResult &)=delete
 
AsyncResultoperator= (const AsyncResult &)=delete
 
bool set (const T &val)
 
bool set (T &&val)
 
get () const
 
move_get ()
 
bool set_error (int err=-1)
 
int get_error () const
 
bool is_done () const
 

Detailed Description

template<class T>
class Cgu::AsyncResult< T >

A thread-safe asynchronous result class.

See also
AsyncQueueDispatch Thread::Future

Cgu::Thread::Future operates on the principle of there being one worker thread per task. In some cases however, it may be better to have a worker thread, or a limited pool of worker threads, executing a larger number of tasks. This can be implemented by having a worker thread or threads waiting on a Cgu::AsyncQueueDispatch object, onto which other threads push tasks represented by std::unique_ptr<const Cgu::Callback::Callback> or Cgu::Callback::SafeFunctor objects.

Where this model is adopted, when a task completes it may report its results by dispatching a further callback to a glib main loop using Cgu::Callback::post(). However, there will also be cases where, rather than passing a result as an event to a main loop, a thread is to to wait for the task to complete. This class is intended to facilitate that. It operates in a way which is similar to the std::promise class in C++11. The thread which wishes to extract a result can call the get() method, which will block until the worker thread has called the set() method or posted an error.

For safety reasons, the get() method returns by value and so will cause that value to be copied once. From version 2.0.11 a move_get() method is provided which will make a move operation instead of a copy if the value type implements a move constructor, but see the documentation on move_get() for the caveats with respect to its use: in particular, if move_get() is to be called by a thread, then get() may not be called by another thread.

Here is a compilable example of a calculator class which runs a dedicated thread on which it carries out all its calculations:

#include <vector>
#include <numeric>
#include <memory>
#include <ostream>
#include <iostream>
using namespace Cgu;
class Calcs {
void do_jobs() {
for (;;) {
std::unique_ptr<const Callback::Callback> job;
jobs.move_pop_dispatch(job);
job->dispatch();
}
}
public:
SharedLockPtr<AsyncResult<double>> mean(const std::vector<double>& nums) {
jobs.emplace(Callback::lambda<>([=]() {
if (nums.empty()) res->set(0.0);
else res->set(std::accumulate(nums.begin(), nums.end(), 0.0)/nums.size());
}));
return res;
}
// ... other calculation methods here
Calcs() {
t = Thread::JoinableHandle(Thread::Thread::start(Callback::make(*this, &Calcs::do_jobs), true),
if (!t.is_managing()) throw "Thread start error";
}
~Calcs() {
jobs.emplace(Callback::lambda<>([]() {throw Thread::Exit();}));
t.join();
}
};
int main () {
Calcs calcs;
auto res1 = calcs.mean({1, 2, 8, 0});
auto res2 = calcs.mean({101, 53.7, 87, 1.2});
// ... do something else
std::cout << res1->get() << std::endl;
std::cout << res2->get() << std::endl;
}

AsyncResult objects cannot be copied by value, and as they need to be visible both to the set()ing and get()ing threads, it will often be easiest to construct them on free store and copy them by smart pointer, as in the example above. However, if the library is compiled with the --with-glib-memory-slices-compat or --with-glib-memory-slices-no-compat configuration options, any AsyncResult object constructed on free store will be constructed in glib memory slices, which are an efficient small object allocator.

Constructor & Destructor Documentation

◆ AsyncResult() [1/2]

template<class T >
Cgu::AsyncResult< T >::AsyncResult ( )
inline
Exceptions
Thread::MutexErrorThe constructor might throw this exception if initialisation of the contained mutex fails. (It is often not worth checking for this, as it means either memory is exhausted or pthread has run out of other resources to create new mutexes.) The constructor will also throw if the default constructor of the result type represented by this object throws.
Thread::CondErrorThe constructor might throw this exception if initialisation of the contained condition variable fails. (It is often not worth checking for this, as it means either memory is exhausted or pthread has run out of other resources to create new condition variables.) The constructor will also throw if the default constructor of the result type represented by this object throws.

Since 2.0.8

◆ ~AsyncResult()

template<class T >
Cgu::AsyncResult< T >::~AsyncResult ( )
inline

◆ AsyncResult() [2/2]

template<class T >
Cgu::AsyncResult< T >::AsyncResult ( const AsyncResult< T > &  )
delete

This class cannot be copied. The copy constructor is deleted.

Since 2.0.8

Member Function Documentation

◆ get()

template<class T >
T Cgu::AsyncResult< T >::get ( ) const
inline

This method gets the stored value represented by the AsyncResult object. It is thread safe. It is a cancellation point if it blocks, and is cancellation safe if the stack unwinds on cancellation. Any number of threads may call this method and block on it. It will not throw unless the copy constructor of the return type throws. It is strongly exception safe.

Returns
the value represented by this object as set by a call to set(). If no such value has been set (and no error has been set) so that is_done() will return false, this method will block until either a value or an error has been set. If an error has been set, this method will return a default constructed object of the template type (and the error can be obtained with get_error()).
Note
1. This method calls Thread::Cond::wait(). Between versions 2.2.3 and 2.2.13 inclusive, Thread::Cond::wait() was marked 'noexcept'. This was a mistake because it prevented a thread being cancelled while in a wait, including in this method (the cancellation pseudo-exception conflicted with the noexcept specifier). This was fixed in version 2.2.14.
2. Question: Couldn't this method return the stored value by lvalue reference to const? Answer: It could. However, because of return value optimization, which will be implemented by any compiler capable of compiling this library, no advantage would be gained by doing so when initializing a local variable with the return value of this method (the copy constructor will only be called once whether returning by value or const reference). The advantage of returning by value is that the call to the copy constructor is forced to be within the AsyncResult object's mutex, so different threads' calls to the copy constructor are serialized, and also with blocked cancellation, so this method is cancellation safe. All calls to this method by different threads are therefore isolated and we do not have to worry about the thread safety of direct access to the stored value via its const methods outside the mutex (which would not be thread safe if the stored value has data members declared mutable) nor about the cancellation safety of the copy constructor. Of course, for objects which do not have mutable data, a hit arises by returning by value in cases where it is not intended to initialize a local variable at all nor to cancel a thread: where, say, only const methods are to be called on the return value (which could be done directly if this method returned by const reference). However, in many use cases this will be mitigated by the move_get() method.

Since 2.0.8

◆ get_error()

template<class T >
int Cgu::AsyncResult< T >::get_error ( ) const
inline

This method is thread safe. It is not a cancellation point. It will not throw.

Returns
the error value set by a call to set_error(), or 0 if no error has been set.

Since 2.0.8

◆ is_done()

template<class T >
bool Cgu::AsyncResult< T >::is_done ( ) const
inline

This method is thread safe. It is not a cancellation point. It will not throw.

Returns
true if set() has been called, or set_error() has been called with a value other than 0, otherwise false.

Since 2.0.8

◆ move_get()

template<class T >
T Cgu::AsyncResult< T >::move_get ( )
inline

This method gets the stored value represented by the AsyncResult object by a move operation, if the type of that value implements a move constructor (otherwise this method does the same as the get() method). It is provided as an option for cases where a move is required for efficiency reasons, but although it may be called by any thread, a move operation may normally only be made once (except where the return type has been designed to be moved more than once for the limited purpose of inspecting a flag indicating whether its value is valid or not). If this method is to be called then no calls to get() by another thread should normally be made. This method is a cancellation point if it blocks, and is cancellation safe if the stack unwinds on cancellation. It will not throw unless the copy or move constructor of the return type throws. It is only exception safe if the return type's move constructor is exception safe.

Returns
The value represented by this object as set by a call to set(). If no such value has been set (and no error has been set) so that is_done() will return false, this method will block until either a value or an error has been set. If an error has been set, until a move operation has been carried out this method will return a default constructed object of the template type (and the error can be obtained with get_error()).
Note
1. This method calls Thread::Cond::wait(). Between versions 2.2.3 and 2.2.13 inclusive, Thread::Cond::wait() was marked 'noexcept'. This was a mistake because it prevented a thread being cancelled while in a wait, including in this method (the cancellation pseudo-exception conflicted with the noexcept specifier). This was fixed in version 2.2.14.
2. Question: Couldn't this method return the stored value by rvalue reference? Answer: It could. However, because of return value optimization, which will be implemented by any compiler capable of compiling this library, no advantage would be gained by doing so when initializing a local variable with the return value of this method (the move constructor will only be called once, and no call will be made to the copy constructor, whether returning by value or rvalue reference). The advantage of returning by value is that the call to the move constructor is forced to be within the AsyncResult object's mutex, so different threads' calls to the move constructor are serialized, and also with blocked cancellation, so this method is cancellation safe. All calls to this method by different threads are therefore isolated and we do not have to worry about the thread safety of the mutating first call to this method, nor about direct access to the stored value via a rvalue reference outside the mutex nor the cancellation safety of the move constructor.

Since 2.0.11

◆ operator=()

template<class T >
AsyncResult& Cgu::AsyncResult< T >::operator= ( const AsyncResult< T > &  )
delete

This class cannot be copied. The assignment operator is deleted.

Since 2.0.8

◆ set() [1/2]

template<class T >
bool Cgu::AsyncResult< T >::set ( const T &  val)
inline

This method sets the value represented by the AsyncResult object, provided that set() has not previously been called and set_error() has not previously been called with a value other than 0. If set() has previously been called or set_error() called with a value other than 0 (so that is_done() will return true) this method does nothing. It is thread safe. It is not a cancellation point. It will not throw unless the copy assignment operator of the value type throws.

Parameters
valThe value which this object is to represent and which calls to get() or a call to move_get() will return. Any thread waiting on get() or move_get() will unblock, and any subsequent calls to is_done() will return true.
Returns
true if the call to this method is effective because set() has not previously been called and set_error() has not previously been called with a value other than 0, otherwise false.

Since 2.0.8

◆ set() [2/2]

template<class T >
bool Cgu::AsyncResult< T >::set ( T &&  val)
inline

This method sets the value represented by the AsyncResult object, provided that set() has not previously been called and set_error() has not previously been called with a value other than 0. If set() has previously been called or set_error() called with a value other than 0 (so that is_done() will return true) this method does nothing. It is thread safe. It is not a cancellation point. It will not throw unless the copy or move assignment operator of the value type throws.

Parameters
valThe value which this object is to represent and which calls to get() or a call to move_get() will return. Any thread waiting on get() or move_get() will unblock, and any subsequent calls to is_done() will return true.
Returns
true if the call to this method is effective because set() has not previously been called and set_error() has not previously been called with a value other than 0, otherwise false.

Since 2.0.8

◆ set_error()

template<class T >
bool Cgu::AsyncResult< T >::set_error ( int  err = -1)
inline

This method sets an error if called with a value other than 0, provided that set() has not previously been called and this method has not previously been called with a value other than 0. If set() has been called or this method previously called with a value other than 0 (so that is_done() will return true), this method does nothing. This method is thread safe. It is not a cancellation point. It will not throw.

Parameters
errThe value which subsequent calls to get_error() will report. If the value of err is 0, or if this method has been called with a value other than 0 or set() has previously been called, this method will do nothing. Otherwise, any thread waiting on get() or move_get() will unblock (they will return a default constructed object of the template type), and any subsequent calls to is_done() will return true.
Returns
true if the call to this method is effective because the error value passed is not 0, set() has not previously been called and this method has not previously been called with a value other than 0, otherwise false.

Since 2.0.8


The documentation for this class was generated from the following file:
Cgu::AsyncQueueDispatch
A thread-safe asynchronous queue with a blocking pop() method.
Definition: async_queue.h:640
Cgu
Definition: application.h:44
Cgu::Thread::JoinableHandle
A class wrapping a Thread::Thread object representing a joinable thread.
Definition: thread.h:472
Cgu::AsyncQueueDispatch::emplace
void emplace(Args &&... args)
Definition: async_queue.h:740
Cgu::Callback::make
CallbackArg< FreeArgs... > * make(T &t, void(T::*func)(FreeArgs...))
Definition: callback.h:1334
shared_ptr.h
Cgu::Callback::CallbackArg::dispatch
virtual void dispatch(typename Cgu::Param< FreeArgs >::ParamType... args) const =0
Cgu::Thread::JoinableHandle::join_on_exit
@ join_on_exit
Definition: thread.h:474
callback.h
This file provides classes for type erasure.
Cgu::AsyncQueueDispatch::move_pop_dispatch
void move_pop_dispatch(value_type &obj)
Definition: async_queue.h:954
Cgu::AsyncResult
A thread-safe asynchronous result class.
Definition: async_result.h:165
Cgu::Thread::Exit
A class which can be thrown to terminate the throwing thread.
Definition: thread.h:855
async_queue.h
This file provides thread-safe asynchronous queue classes.
async_result.h
This file provides a thread-safe asynchronous result class.
Cgu::Thread::JoinableHandle::is_managing
bool is_managing()
Cgu::Thread::JoinableHandle::join
bool join()
Cgu::SharedLockPtr
This is a smart pointer for managing the lifetime of objects allocated on freestore,...
Definition: shared_ptr.h:644
thread.h
Cgu::Thread::Thread::start
static std::unique_ptr< Cgu::Thread::Thread > start(const Cgu::Callback::Callback *cb, bool joinable)