async_result.h
Go to the documentation of this file.
/* Copyright (C) 2012 and 2013 Chris Vine
The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License, version 2.1, for more details.
You should have received a copy of the GNU Lesser General Public
License, version 2.1, along with this library (see the file LGPL.TXT
which came with this source code package in the c++-gtk-utils
sub-directory); if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
However, it is not intended that the object code of a program whose
source code instantiates a template from this file or uses macros or
inline functions (of any length) should by reason only of that
instantiation or use be subject to the restrictions of use in the GNU
Lesser General Public License. With that in mind, the words "and
macros, inline functions and instantiations of templates (of any
length)" shall be treated as substituted for the words "and small
macros and small inline functions (ten lines or less in length)" in
the fourth paragraph of section 5 of that licence. This does not
affect any other reason why object code may be subject to the
restrictions in that licence (nor for the avoidance of doubt does it
affect the application of section 2 of that licence to modifications
of the source code in this file).
*/
/**
* @file async_result.h
* @brief This file provides a thread-safe asynchronous result class.
*/
#ifndef CGU_ASYNC_RESULT_H
#define CGU_ASYNC_RESULT_H
#include <utility> // for std::move
#include <c++-gtk-utils/mutex.h>
#include <c++-gtk-utils/thread.h>
#include <c++-gtk-utils/cgu_config.h>
namespace Cgu {
/**
* @class AsyncResult async_result.h c++-gtk-utils/async_result.h
* @brief A thread-safe asynchronous result class.
* @sa 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:
*
* @code
* #include <vector>
* #include <numeric>
* #include <memory>
* #include <ostream>
* #include <iostream>
*
* #include <c++-gtk-utils/async_result.h>
* #include <c++-gtk-utils/async_queue.h>
* #include <c++-gtk-utils/shared_ptr.h>
* #include <c++-gtk-utils/thread.h>
* #include <c++-gtk-utils/callback.h>
*
* using namespace Cgu;
*
* class Calcs {
* AsyncQueueDispatch<std::unique_ptr<const Callback::Callback>> jobs;
* Thread::JoinableHandle t;
*
* 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) {
* SharedLockPtr<AsyncResult<double>> res(new AsyncResult<double>);
* 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),
* Thread::JoinableHandle::join_on_exit);
* 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;
*
* }
* @endcode
*
* 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.
*/
template <class T> class AsyncResult {
T res;
bool done;
int error;
mutable Thread::Mutex mutex;
mutable Thread::Cond cond;
public:
/**
* @exception Thread::MutexError The 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.
* @exception Thread::CondError The 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(): res(), done(false), error(0) {}
~AsyncResult() {
// lock and unlock the mutex in the destructor so that we have an
// acquire operation to ensure that when this object is destroyed
// memory is synchronised, so any thread may destroy this object
Thread::Mutex::Lock lock{mutex};
}
// AsyncResult objects cannot be copied - they are mainly
// intended to pass a result between two known threads
/**
* This class cannot be copied. The copy constructor is deleted.
*
* Since 2.0.8
*/
AsyncResult(const AsyncResult&) = delete;
/**
* This class cannot be copied. The assignment operator is deleted.
*
* Since 2.0.8
*/
AsyncResult& operator=(const AsyncResult&) = delete;
/**
* 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.
* @param val The 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.
* @return 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
*/
bool set(const T& val) {
Thread::Mutex::Lock lock{mutex};
if (!done) {
res = val;
done = true;
cond.broadcast();
return true;
}
return false;
}
/**
* 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.
* @param val The 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.
* @return 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
*/
bool set(T&& val) {
Thread::Mutex::Lock lock{mutex};
if (!done) {
res = std::move(val);
done = true;
cond.broadcast();
return true;
}
return false;
}
/**
* 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.
* @return 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 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
*/
T get() const {
Thread::Mutex::Lock lock{mutex};
while (!done) cond.wait(mutex);
Thread::CancelBlock b;
return res;
}
/**
* 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.
* @return 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 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
*/
T move_get() {
Thread::Mutex::Lock lock{mutex};
while (!done) cond.wait(mutex);
Thread::CancelBlock b;
return std::move(res);
}
/**
* 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.
* @param err The 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.
* @return 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
*/
bool set_error(int err = -1) {
Thread::Mutex::Lock lock{mutex};
if (err && !done) {
error = err;
done = true;
cond.broadcast();
return true;
}
return false;
}
/**
* This method is thread safe. It is not a cancellation point. It
* will not throw.
* @return the error value set by a call to set_error(), or 0 if no
* error has been set.
*
* Since 2.0.8
*/
int get_error() const {
Thread::Mutex::Lock lock{mutex};
return error;
}
/**
* This method is thread safe. It is not a cancellation point. It
* will not throw.
* @return true if set() has been called, or set_error() has been
* called with a value other than 0, otherwise false.
*
* Since 2.0.8
*/
bool is_done() const {
Thread::Mutex::Lock lock{mutex};
return done;
}
/* Only has effect if --with-glib-memory-slices-compat or
* --with-glib-memory-slices-no-compat option picked */
CGU_GLIB_MEMORY_SLICES_FUNCS
};
} // namespace Cgu
#endif