Document Number: | N2709=08-0219 |
Date: | 2008-07-28 |
Project: | Programming Language C++ |
Table of Contents
This proposal is based on the proposal for std::packaged_task
in N2561 and N2627. It has been separated from the futures proposal at the
request of the LWG, and revised in light of LWG comments at the Sophia-Antipolis
meeting in June 2008.
A std::packaged_task
provides a means of binding a function to an asynchronous future values so
that when the function is invoked, the asynchronous future value becomes populated,
rather than the return value or exception being propagated to the caller.
This provides an answer to the perennial question "how do I return a value
from a thread?": package the function you wish to run as a std::packaged_task
and pass the packaged task to the thread constructor. The future retrieved
from the packaged task can then be used to obtain the return value. If the
function throws an exception, that is stored in the future in place of the
return value.
int calculate_the_answer_to_life_the_universe_and_everything() { return 42; } std::packaged_task<int()> pt(calculate_the_answer_to_life_the_universe_and_everything); std::unique_future<int> fi=pt.get_future(); std::thread task(std::move(pt)); // launch task on a thread fi.wait(); // wait for it to finish assert(fi.is_ready()); assert(fi.has_value()); assert(!fi.has_exception()); assert(fi.get_state()==std::future_state::ready); assert(fi.get()==42);
Of course, this is not the only use for std::packaged_task
:
it can be used to store a task for later invocation, much like std::function
.
With this revision, std::packaged_task
has been remodelled to be more in line with std::function
than in N2627: the function call operator now accepts arguments which are passed
on to the encapsulated function, and the template parameter is a function call
signature. The key difference is that the result of the function invocation
is stored in the associated future rather than returned directly to the caller.
Though std::packaged_task
doesn't provide any features that can't be built using a combination of std::promise
from N2671 and std::function
, I believe it to be a common-enough
usage scenario that it warrants being standardized. In particular, the equivalent
code using std::promise
is cumbersome. For comparison, let's
look at what's needed to spawn a task on a new thread and retrieve the return
value in a std::unique_future
.
template<typename F> std::unique_future<typename std::result_of<F()>::type> spawn_task(F f) { typedef typename std::result_of<F()>::type result_type; std::packaged_task<result_type()> task(std::move(f)); std::unique_future<result_type> res(task.get_future()); std::thread(std::move(task)); return res; }
This builds a std::packaged_task
for the function, obtains a std::unique_future
for the result, moves that task onto a thread (which is automatically detached),
and returns the future.
This can easily be extended to handle additional arguments.
template<typename F> std::unique_future<typename std::result_of<F()>::type> spawn_task(F f) { typedef typename std::result_of<F()>::type result_type; struct local_task { std::promise<result_type> promise; F func; local_task(local_task const& other)=delete; local_task(F func_): func(func_) {} local_task(local_task&& other): promise(std::move(other.promise)), f(std::move(other.f)) {} void operator() { try { promise.set_value(f()); } catch(...) { promise.set_exception(std::current_exception()); } } }; local_task task(std::move(f)); std::unique_future<result_type> res(task.promise.get_future()); std::thread(std::move(task)); return res; }
local_task
performs the same
function as std::packaged_task
,
but in a much more limited manner. It is easy to imagine users getting it
wrong by forgetting to catch the exception, or creating the promise as a
local variable and using a lambda as the thread function (and thus getting
a dangling reference to the promise).
std::packaged_task
is intended to be usable as a basic building block of a thread pool. Whereas
with spawn_task
as presented
above the task is immediately associated with a thread, in a thread pool
the task could be put on a queue to be executed by a worker thread at a later
point. The worker threads would then only need to know they were executing
tasks from the queue, without having to worry about what the functions were,
or the details of storing the results.
Advanced thread pool implementations would potentially require access to
the internals of std::packaged_task
and/or std::unique_future
, and would therefore have
to be provided as part of the same implementation, or require extensions
to the interfaces. Such extensions are left for a later proposal, as thread
pools are not being standardized for C++0x.
Though std::packaged_task
is ideally suited to multi-threaded usage, it can also be used in a single-threaded
scenario too. In this case, it is important not to wait()
for the future result, since there isn't
a background thread to prepare the result, but it does allow an unspecified
task to be invoked only when necessary.
std::packaged_task<int(int,int)> task(create_task()); std::unique_future<int> future; while(!done()) { int a,b; if(future_value_needed()) { if(!future.is_ready()) task(a,b); do_something(future.get()); } }
If the task needs to be invoked multiple times, but should only be invoked
when necessary, then reset()
can be called after the task has been invoked in order to reset the state
for a subsequent invocation. It is important to note that in this case, a
fresh std::unique_future
must be obtained (by calling
get_future()
)
after the call to reset()
:
any futures already obtained will continue to refer to the result of the
prior invocation.
template<class> class packaged_task; // undefined template<class R,class... ArgTypes> class packaged_task<R(ArgTypes...)> { public: typedef R result_type; // construction and destruction packaged_task(); template <class F> explicit packaged_task(F const& f); explicit packaged_task(R(*f)()); template <class F> explicit packaged_task(F&& f); template <class F, class Allocator> explicit packaged_task(allocator_arg_t,Allocator const& a, F f); template <class F, class Allocator> explicit packaged_task(allocator_arg_t,Allocator const& a, F&& f); ~packaged_task(); // no copy packaged_task(packaged_task&) = delete; packaged_task& operator=(packaged_task&) = delete; // move support packaged_task(packaged_task&& other); packaged_task& operator=(packaged_task&& other); void swap(packaged_task&& other); explicit operator bool() const; // result retrieval unique_future<R> get_future(); // execution void operator()(ArgTypes... ); void reset(); };
template<typename F> packaged_task(F const &f); packaged_task(R(*f)()); template<typename F> packaged_task(F&&f);
f()
is a valid expression with a return type convertible to R
. Invoking a copy of f
shall behave the same as invoking
f
.
Constructs a new std::packaged_task
with a copy
of f
stored as the
associated task.
Any exceptions thrown by the copy (or move) constructor of f
. std::bad_alloc
if memory for the internal data structures could not be allocated.
packaged_task(packaged_task && other);
Constructs a new std::packaged_task
, and transfers
ownership of the task associated with other
to *this
,
leaving other
with
no associated task.
Nothing.
packaged_task& operator=(packaged_task && other);
Transfers ownership of the task associated with other
to *this
,
leaving other
with
no associated task. If there was already a task associated with *this
,
and that task has not been invoked, sets any futures associated with
that task to ready with a std::broken_promise
exception as the result.
Nothing.
~packaged_task();
Destroys *this
.
If there was a task associated with *this
, and that task has not been invoked,
sets any futures associated with that task to ready
with a std::broken_promise
exception as the result.
Nothing.
explicit operator bool() const
true
if *this
has an associated task, otherwise false
.
nothing.
unique_future<R> get_future();
Returns a std::unique_future
associated with the
result of the task associated with *this
.
std::bad_function_call
if !*this
.
std::future_already_retrieved
if the future
associated with the task has already been retrieved.
void operator()(ArgTypes... args);
INVOKE (f, t1, t2, ..., tN, R)
, where f
is the task associated with *this
and t1
,
t2
, ...
,
tN
are the values in
args...
.
If the task returns normally, the return value is stored as the asynchronous
result associated with *this
, otherwise the exception thrown
is stored. Any threads blocked waiting for the asynchronous result
associated with this task are woken.
All futures waiting on the asynchronous result are ready
std::bad_function_call
if !*this
.
std::task_already_started
if the task
has already been invoked.
void reset();
Return the task to a state as-if a newly-constructed instance has just
been assigned to *this
by *this=packaged_task(std::move(f))
where f
is the task associated with *this
. If there was already a task associated
with *this
,
and that task has not been invoked, sets any futures associated with
that task to ready with a std::broken_promise
exception as the result. get_future()
may now be called again for *this
.
*this
has no associated futures. If *this
had an associated task, then the
associated task is a copy of the old task.
std::bad_alloc
if memory for the internal
data structures of the new asynchronous result could not be allocated.