A Simple Asynchronous Call

ISO/IEC JTC1 SC22 WG21 N2996 = 09-0186 - 2009-10-23

Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
Herb Sutter, hsutter@microsoft.com

This paper is a consensus of "A simple async() (revision 1)" N2970 = 09-0160 - 2009-09-26 and "An Asynchronous Call for C++" N2973 = 09-0163 - 2009-09-27.

Introduction
Acknowledgements
Proposed Wording
    30.6.1 Overview [futures.overview]
    30.6.5 Class template unique_future [futures.unique_future]
    30.6.6 Class template shared_future [futures.shared_future]
    30.6.? Function template async [futures.async]

Introduction

This paper represents a consensus proposal on an asynchronous execution facility. For rationale see papers N2970 and N2973. For further background see paper N2974.

The following changes have been made with respect to N2970 and N2973.

Acknowledgements

This solution derives from an extensive discussion on the C++ threads standardisation <cpp-threads@decadentplace.org.uk> mailing list. Thanks to the following contributors to the discussion on this topic: Hans Boehm, Beman Dawes, Peter Dimov, Pablo Halpern, Howard Hinnant, Oliver Kowalke, Doug Lea, Arch Robison, Bjarne Stroustrup, Alexander Terekhov, and Anthony Williams. Further discussion at the October 2009 meeting include contributors beyond our ability to recount. We gratefully acknowledge their contribution.

Proposed Wording

The proposed wording is as follows. It consists primarily of two new subsections.

30.6.1 Overview [futures.overview]

Add to the synopsis the appropriate entries from the following sections.

30.6.5 Class template unique_future [futures.unique_future]

Edit the synopsis as follows. The edit removes query functions on unique_future.


namespace std {
  template <class R>
  class unique_future {
  public:
    unique_future(unique_future &&);
    unique_future(const unique_future& rhs) = delete;
    ~unique_future();
    unique_future& operator=(const unique_future& rhs) = delete;
    // retrieving the value
    see below get();
    // functions to check state and wait for ready
    bool is_ready() const;
    bool has_exception() const;
    bool has_value() const;
    void wait() const;
    template <class Rep, class Period>
      bool wait_for(
        const chrono::duration<Rep, Period>& rel_time) const;
    template <class Clock, class Duration>
      bool wait_until(
        const chrono::time_point<Clock, Duration>& abs_time) const;
  };
}

After paragraph 4, add a new paragraph as follows. This paragraph is a requirement on the destructor.

Synchronization: If no other future refers to the associated state, and that state is associated with a thread created by an async call ([futures.async]), as if associated-thread.join().

After paragraph 5, add a new paragraph as follows. This paragraph is a requirement on get().

Effects: As if wait().

Delete paragraph 6. This synchronization now happens as a consequence of "as if" wait().

Synchronization: if *this is associated with a promise object, the completion of set_value() or set_exception() to that promise happens before (1.10) get() returns.

Delete member functions is_ready, has_exception, and has_value.

bool is_ready() const;

Returns: true only if the associated state holds a value or an exception ready for retrieval.

Remark: the return value is unspecified after a call to get().

bool has_exception() const;
Returns: true only if is_ready() == true and the associated state contains an exception.
bool has_value() const;
Returns: true only if is_ready() == true and the associated state contains a value.

Edit paragraph 13 as follows.

Effects: if the associated state contains a deferred function, then execute the deferred function. Otherwise, blocks until *this is ready.

Edit paragraph 14 as follows.

Synchronization: if *this is associated with a promise object, the completion of set_value() or set_exception() to that promise happens before (1.10) wait() returns. If the future is associated with a thread created by an async call ([futures.async]), as if associated-thread.join().

Edit the wait_for() prototype as follows.


template <class Rep, class period Period>
void wait_for(const chrono::duration<Rep, Period>& rel_time) const;

Edit paragraph 16 as follows.

Effects: if the associated state contains a deferred function, then the behavior is unspecified. Otherwise, blocks until *this is ready or until rel_time has elapsed. If *this is ready, as if wait().

30.6.6 Class template shared_future [futures.shared_future]

Edit the synopsis as follows.


namespace std {
  template <class R>
  class shared_future {
  public:
    shared_future(const shared_future& rhs);
    shared_future(unique_future<R>&&);
    ~shared_future();
    shared_future & operator=(const shared_future& rhs) = delete;
    // retrieving the value
    see below get() const;
    // functions to check state and wait for ready
    bool is_ready() const;
    bool has_exception() const;
    bool has_value() const;
    void wait() const;
    template <class Rep, class Period>
    bool wait_for(const chrono::duration<Rep, Period>& rel_time) const;
    template <class Clock, class Duration>
    bool wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;
  };
}

After paragraph 5, add a new paragraph as follows. This paragraph is a note on the destructor.

Synchronization: If no other future refers to the associated state, and that state is associated with a thread created by an async call ([futures.async]), as if associated-thread.join().

After paragraph 6, add a new paragraph as follows. This paragraph is a requirement on get().

Effects: As if wait().

Remove the member functions is_ready, has_exception, has_value.

bool is_ready() const;
Returns: true only if the associated state holds a value or an exception ready for retrieval.
bool has_exception() const;
Returns: true only if is_ready() == true and the associated state contains an exception.
bool has_value() const;
Returns: true only if is_ready() == true and the associated state contains a value.

Edit paragraph 13 as follows. This paragraph describes wait().

Effects: if the associated state contains a deferred function, then execute the deferred function. Otherwise, blocks until *this is ready.

Edit paragraph 14 as follows.

Synchronization: if *this is associated with a promise object, the completion of set_value() or set_exception() to that promise happens before (1.10) get() returns. If the future is associated with a thread created by an async call ([futures.async]), as if associated-thread.join().

Edit the wait_for() prototype as follows.


template <class Rep, class period Period>
void wait_for(const chrono::duration<Rep, Period>& rel_time) const;

Edit paragraph 16 as follows. This paragraph describes wait_for().

Effects: if the associated state contains a deferred function, then the behavior is unspecified. Otherwise, blocks until *this is ready or until rel_time has elapsed.

30.6.? Function template async [futures.async]

Add the following section.


enum class launch { any, async, sync };

template<class F, class ...Args>
    unique_future<typename F::result_type>
    async( F&& f, Args&&... args );

template<class F, class ...Args>
    unique_future<typename F::result_type>
    async( launch policy, F&& f, Args&&... args );

Requires: F and each type Ti in Args shall be CopyConstructible if an lvalue and otherwise MoveConstructible. INVOKE (f, w1, w2, ..., wN) (20.7.2) shall be a valid expression for some values w1, w2, ..., wN, where N == sizeof...(Args).

Effects: Constructs an object of type unique_future<typename F::result_type> ([futures.unique_future]). If the policy parameter is not present, then as if the policy parameter were launch::any. If policy is launch::async, executes INVOKE (f, w1, w2, ..., wN) as if in a new thread of execution. Any return value is captured by the unique_future. Any exception not caught by f is captured by the unique_future. The thread is associated with the unique_future, and affects the behavior of the unique_future. If policy is launch::sync, then INVOKE (f, w1, w2, ..., wN) is associated with the returned unique_future. The invocation is said to be deferred. If policy is launch::any, the implementation may choose either policy above at any call to async. [Note: Implementations should defer invocations when no more concurrency can be effectively exploited. —end note]

Synchronization: The invocation of the async happens before (1.10 [intro.multithread]) the invocation of f. [Note: This statement applies even when the corresponding unique_future is moved to another thread. —end note]

Throws: std::system_error if policy is launch::async and the implementation is unable to start a new thread.

Error conditions:resource_unavailable_try_again — if policy is launch::async and either the system lacked the necessary resources to create another thread, or the system-imposed limit on the number of threads in a process would be exceeded.

[Example: Two items of work below may be executed concurrently.


extern int work1(int value);
extern int work2(int value);
int work(int value) {
  auto handle = std::async( [=]{ return work2(value); } );
  int tmp = work1(value);
  return tmp + handle.get();
}

end example:] [Note: The statement


return work1(value) + handle.get();

might not result in concurrency because get() may be evaluated before work1(), thus forcing work2 to be evaluated before work1(). —end note:]