c++-gtk-utils
async_result.h
Go to the documentation of this file.
1 /* Copyright (C) 2012 Chris Vine
2 
3 The library comprised in this file or of which this file is part is
4 distributed by Chris Vine under the GNU Lesser General Public
5 License as follows:
6 
7  This library is free software; you can redistribute it and/or
8  modify it under the terms of the GNU Lesser General Public License
9  as published by the Free Software Foundation; either version 2.1 of
10  the License, or (at your option) any later version.
11 
12  This library is distributed in the hope that it will be useful, but
13  WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Lesser General Public License, version 2.1, for more details.
16 
17  You should have received a copy of the GNU Lesser General Public
18  License, version 2.1, along with this library (see the file LGPL.TXT
19  which came with this source code package in the c++-gtk-utils
20  sub-directory); if not, write to the Free Software Foundation, Inc.,
21  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 
23 However, it is not intended that the object code of a program whose
24 source code instantiates a template from this file or uses macros or
25 inline functions (of any length) should by reason only of that
26 instantiation or use be subject to the restrictions of use in the GNU
27 Lesser General Public License. With that in mind, the words "and
28 macros, inline functions and instantiations of templates (of any
29 length)" shall be treated as substituted for the words "and small
30 macros and small inline functions (ten lines or less in length)" in
31 the fourth paragraph of section 5 of that licence. This does not
32 affect any other reason why object code may be subject to the
33 restrictions in that licence (nor for the avoidance of doubt does it
34 affect the application of section 2 of that licence to modifications
35 of the source code in this file).
36 
37 */
38 
39 /**
40  * @file async_result.h
41  * @brief This file provides a thread-safe asynchronous result class.
42  */
43 
44 #ifndef CGU_ASYNC_RESULT_H
45 #define CGU_ASYNC_RESULT_H
46 
47 #include <utility> // for std::move
48 
49 #include <c++-gtk-utils/mutex.h>
50 #include <c++-gtk-utils/thread.h>
52 
53 
54 namespace Cgu {
55 
56 /**
57  * @class AsyncResult async_result.h c++-gtk-utils/async_result.h
58  * @brief A thread-safe asynchronous result class.
59  * @sa AsyncQueueDispatch Thread::Future
60  *
61  * Cgu::Thread::Future operates on the principle of there being one
62  * worker thread per task. In some cases however, it may be better to
63  * have a worker thread, or a limited pool of worker threads,
64  * executing a larger number of tasks. This can be implemented by
65  * having a worker thread or threads waiting on a
66  * Cgu::AsyncQueueDispatch object, onto which other threads push tasks
67  * represented by Cgu::Callback::SafeFunctor objects.
68  *
69  * Where this model is adopted, when a task completes it may report
70  * its results by dispatching a further callback to a glib main loop
71  * using Cgu::Callback::post(). However, there will also be cases
72  * where, rather than passing a result as an event to a main loop, a
73  * thread is to to wait for the task to complete. This class is
74  * intended to facilitate that. It operates in a way which is similar
75  * to the std::promise class in C++11. The thread which wishes to
76  * extract a result can call the get() method, which will block until
77  * the worker thread has called the set() method or posted an error.
78  *
79  * For safety reasons, the get() method returns by value and so will
80  * cause that value to be copied once. From version 2.0.11 a
81  * move_get() method is provided which will attempt a move operation
82  * instead of a copy, but see the documentation on move_get() for the
83  * caveats with respect to its use: in particular, if move_get() is to
84  * be called by a thread, then get() may not be called by another
85  * thread.
86  *
87  * Here is a compilable example of a calculator class which runs a
88  * dedicated thread on which it carries out all its calculations:
89  *
90  * @code
91  * #include <vector>
92  * #include <numeric>
93  * #include <ostream>
94  * #include <iostream>
95  *
96  * #include <c++-gtk-utils/async_result.h>
97  * #include <c++-gtk-utils/async_queue.h>
98  * #include <c++-gtk-utils/shared_ptr.h>
99  * #include <c++-gtk-utils/thread.h>
100  * #include <c++-gtk-utils/callback.h>
101  *
102  * using namespace Cgu;
103  *
104  * class Calcs {
105  * AsyncQueueDispatch<Callback::SafeFunctor> jobs;
106  * Thread::JoinableHandle t;
107  *
108  * void do_jobs() {
109  * for (;;) {
110  * Callback::SafeFunctor job;
111  * jobs.move_pop_dispatch(job);
112  * job();
113  * }
114  * }
115  * public:
116  *
117  * SharedLockPtr<AsyncResult<double>> mean(const std::vector<double>& nums) {
118  * SharedLockPtr<AsyncResult<double>> res(new AsyncResult<double>);
119  * jobs.emplace(Callback::lambda<>([=]() {
120  * if (nums.empty()) res->set(0.0);
121  * else res->set(std::accumulate(nums.begin(), nums.end(), 0.0)/nums.size());
122  * return;
123  * }));
124  * return res;
125  * }
126  *
127  * // ... other calculation methods here
128  *
129  * Calcs() {
130  * t = Thread::JoinableHandle(Thread::Thread::start(Callback::make(*this, &Calcs::do_jobs), true),
131  * Thread::JoinableHandle::join_on_exit);
132  * if (!t.is_managing()) throw "Thread start error";
133  * }
134  * ~Calcs() {
135  * jobs.emplace(Callback::lambda<>([]() {throw Thread::Exit();}));
136  * t.join();
137  * }
138  * };
139  *
140  * int main () {
141  *
142  * Calcs calcs;
143  * auto res1 = calcs.mean(std::vector<double>({1, 2, 8, 0}));
144  * auto res2 = calcs.mean(std::vector<double>({101, 53.7, 87, 1.2}));
145  *
146  * // ... do something else
147  *
148  * std::cout << res1->get() << std::endl;
149  * std::cout << res2->get() << std::endl;
150  *
151  * }
152  * @endcode
153  *
154  * AsyncResult objects cannot be copied by value, and as they need to
155  * be visible both to the set()ing and get()ing threads, it will often
156  * be easiest to construct them on free store and copy them by smart
157  * pointer, as in the example above. However, if the library is
158  * compiled with the --with-glib-memory-slices-compat or
159  * --with-glib-memory-slices-no-compat configuration options, any
160  * AsyncResult object constructed on free store will be constructed in
161  * glib memory slices, which are an efficient small object allocator.
162  */
163 
164 template <class T> class AsyncResult {
165  T res;
166  bool done;
167  int error;
168  mutable Thread::Mutex mutex;
169  mutable Thread::Cond cond;
170 
171 public:
172 /**
173  * @exception Thread::MutexError The constructor might throw this
174  * exception if initialisation of the contained mutex fails. (It is
175  * often not worth checking for this, as it means either memory is
176  * exhausted or pthread has run out of other resources to create new
177  * mutexes.) The constructor will also throw if the default
178  * constructor of the result type represented by this object throws.
179  * @exception Thread::CondError The constructor might throw this
180  * exception if initialisation of the contained condition variable
181  * fails. (It is often not worth checking for this, as it means
182  * either memory is exhausted or pthread has run out of other
183  * resources to create new condition variables.) The constructor will
184  * also throw if the default constructor of the result type
185  * represented by this object throws.
186  *
187  * Since 2.0.8
188  */
189  AsyncResult(): res(), done(false), error(0) {}
190 
192  // lock and unlock the mutex in the destructor so that we have an
193  // acquire operation to ensure that when this object is destroyed
194  // memory is synchronised, so any thread may destroy this object
195  Thread::Mutex::Lock lock{mutex};
196  }
197 
198  // AsyncResult objects cannot be copied - they are mainly
199  // intended to pass a result between two known threads
200  /**
201  * This class cannot be copied. The copy constructor is deleted.
202  *
203  * Since 2.0.8
204  */
205  AsyncResult(const AsyncResult&) = delete;
206 
207  /**
208  * This class cannot be copied. The assignment operator is deleted.
209  *
210  * Since 2.0.8
211  */
212  AsyncResult& operator=(const AsyncResult&) = delete;
213 
214  /**
215  * This method sets the value represented by the AsyncResult object,
216  * provided that set() has not previously been called and
217  * set_error() has not previously been called with a value other
218  * than 0. If set() has previously been called or set_error()
219  * called with a value other than 0 (so that is_done() will return
220  * true) this method does nothing. It is thread safe. It is not a
221  * cancellation point. It will not throw unless the copy assignment
222  * operator of the value type throws.
223  * @param val The value which this object is to represent and which
224  * calls to get() or a call to move_get() will return. Any thread
225  * waiting on get() or move_get() will unblock, and any subsequent
226  * calls to is_done() will return true.
227  * @return true if the call to this method is effective because
228  * set() has not previously been called and set_error() has not
229  * previously been called with a value other than 0, otherwise
230  * false.
231  *
232  * Since 2.0.8
233  */
234  bool set(const T& val) {
235  Thread::Mutex::Lock lock{mutex};
236  if (!done) {
237  res = val;
238  done = true;
239  cond.broadcast();
240  return true;
241  }
242  return false;
243  }
244 
245  /**
246  * This method sets the value represented by the AsyncResult object,
247  * provided that set() has not previously been called and
248  * set_error() has not previously been called with a value other
249  * than 0. If set() has previously been called or set_error()
250  * called with a value other than 0 (so that is_done() will return
251  * true) this method does nothing. It is thread safe. It is not a
252  * cancellation point. It will not throw unless the copy or move
253  * assignment operator of the value type throws.
254  * @param val The value which this object is to represent and which
255  * calls to get() or a call to move_get() will return. Any thread
256  * waiting on get() or move_get() will unblock, and any subsequent
257  * calls to is_done() will return true.
258  * @return true if the call to this method is effective because
259  * set() has not previously been called and set_error() has not
260  * previously been called with a value other than 0, otherwise
261  * false.
262  *
263  * Since 2.0.8
264  */
265  bool set(T&& val) {
266  Thread::Mutex::Lock lock{mutex};
267  if (!done) {
268  res = std::move(val);
269  done = true;
270  cond.broadcast();
271  return true;
272  }
273  return false;
274  }
275 
276  /**
277  * This method gets the value represented by the AsyncResult object.
278  * It is thread safe. It is a cancellation point if it blocks, and
279  * is cancellation safe if the stack unwinds on cancellation. Any
280  * number of threads may call this method and block on it. It will
281  * not throw unless the copy constructor of the return type throws.
282  * It is strongly exception safe.
283  * @return the value represented by this object as set by a call to
284  * set(). If no such value has been set (and no error has been set)
285  * so that is_done() will return false, this method will block until
286  * either a value or an error has been set. If an error has been
287  * set, this method will return a default constructed object of the
288  * template type (and the error can be obtained with get_error()).
289  *
290  * Since 2.0.8
291  */
292  T get() const {
293  Thread::Mutex::Lock lock{mutex};
294  while (!done) cond.wait(mutex);
296  return res;
297  }
298 
299  /**
300  * This method gets the value represented by the AsyncResult object
301  * by a move operation, if the type of that value implements a move
302  * constructor (otherwise this method does the same as the get()
303  * method). It is provided as an option for cases where a move is
304  * required for efficiency reasons, but although it may be called by
305  * any thread, a move operation may normally only be made once
306  * (except where the return type has been designed to be moved more
307  * than once for the limited purpose of inspecting a flag indicating
308  * whether its value is valid or not). If this method is to be
309  * called then no calls to get() by another thread should normally
310  * be made. This method is a cancellation point if it blocks, and
311  * is cancellation safe if the stack unwinds on cancellation. It
312  * will not throw unless the copy or move constructor of the return
313  * type throws. It is only exception safe if the return type's move
314  * constructor is exception safe.
315  * @return The value represented by this object as set by a call to
316  * set(). If no such value has been set (and no error has been set)
317  * so that is_done() will return false, this method will block until
318  * either a value or an error has been set. If an error has been
319  * set, until a move operation has been carried out this method will
320  * return a default constructed object of the template type (and the
321  * error can be obtained with get_error()).
322  *
323  * Since 2.0.11
324  */
325  T move_get() {
326  Thread::Mutex::Lock lock{mutex};
327  while (!done) cond.wait(mutex);
329  return std::move(res);
330  }
331 
332  /**
333  * This method sets an error if called with a value other than 0,
334  * provided that set() has not previously been called and this
335  * method has not previously been called with a value other than 0.
336  * If set() has been called or this method previously called with a
337  * value other than 0 (so that is_done() will return true), this
338  * method does nothing. This method is thread safe. It is not a
339  * cancellation point. It will not throw.
340  * @param err The value which subsequent calls to get_error() will
341  * report. If the value of err is 0, or if this method has been
342  * called with a value other than 0 or set() has previously been
343  * called, this method will do nothing. Otherwise, any thread
344  * waiting on get() or move_get() will unblock (they will return a
345  * default constructed object of the template type), and any
346  * subsequent calls to is_done() will return true.
347  * @return true if the call to this method is effective because the
348  * error value passed is not 0, set() has not previously been called
349  * and this method has not previously been called with a value other
350  * than 0, otherwise false.
351  *
352  * Since 2.0.8
353  */
354  bool set_error(int err = -1) {
355  Thread::Mutex::Lock lock{mutex};
356  if (err && !done) {
357  error = err;
358  done = true;
359  cond.broadcast();
360  return true;
361  }
362  return false;
363  }
364 
365  /**
366  * This method is thread safe. It is not a cancellation point. It
367  * will not throw.
368  * @return the error value set by a call to set_error(), or 0 if no
369  * error has been set.
370  *
371  * Since 2.0.8
372  */
373  int get_error() const {
374  Thread::Mutex::Lock lock{mutex};
375  return error;
376  }
377 
378  /**
379  * This method is thread safe. It is not a cancellation point. It
380  * will not throw.
381  * @return true if set() has been called, or set_error() has been
382  * called with a value other than 0, otherwise false.
383  *
384  * Since 2.0.8
385  */
386  bool is_done() const {
387  Thread::Mutex::Lock lock{mutex};
388  return done;
389  }
390 
391 /* Only has effect if --with-glib-memory-slices-compat or
392  * --with-glib-memory-slices-no-compat option picked */
394 };
395 
396 } // namespace Cgu
397 
398 #endif