Tuesday, February 25, 2025

Execution Utility Classes

Overview
These classes store the result or exception to be retrieved asynchronously.

Details
promise
promise is a template class that is used for storing the result of type R or an exception in a shared state that can be accessed asynchronously from a future object. 
A caller creates a promise object and creates a future object from it. Later the promise object is passed to the asynchronous execution using a  thread object for example. The thread function sets the result or the exception in the shared state. The shared state is marked ready and all the waiting threads are released.
Later the caller extracts the result or the exception from the future object created earlier. 
The lifetime of the shared state lasts at least until the last object with which it is associated releases it or is destroyed. Therefore it can survive the promise object that obtained it in the first place if associated also to a future.
syntax
template< class R > class promise

//Non-void specialization, used to communicate objects between threads.
template< class R > class promise<R&>

//void specialization, used to communicate stateless events.
template<> class promise<void>

constructors
NameDescription
promise()Default constructor. Constructs the promise with an empty shared state.

Example
//returns int 
promise<int> p;

//returns void
promise<void> p2;

methods
The following lists commonly used methods.
NameDescription
future<R> get_future()Returns a future associated with the promised result. Returns a future object associated with the object's shared state.
The future object returned can access the value or exception set on the shared state by the promise object once this is ready.
Only one future object can be retrieved for each promise shared state.
After this function has been called, the promise is expected to make its shared state ready at some point (by setting a value or an exception), otherwise it is automatically made ready on destruction containing an exception of type future_error (with a broken_promise error condition).
  1. void set_value(const R& value)
  2. void set_value( R&& value)
  3. void set_value( R& value)
  4. void set_value()
  1. sets lvalue into the shared state
  2. sets rvalue into the shared state
  3. specialization. sets reference value into the shared state
  4. specialization for void returns. 
All the above also makes the state ready. Calling this function twice throws exception std::future_error: Promise already satisfied
void set_exception (exception_ptr p)
Atomically stores the exception pointer p into the shared state and makes the state ready.
If a future object that is associated to the same shared state is waiting on a call to future::get, it stays blocked until the thread ends. Once the thread ends, it throws the exception object pointed by p.
Notice that calling this function already sets a value in the shared state, and any call that modifies this value between this call and the end of the thread will throw future_error with promise_already_satisfied as error condition.
  1. void set_value_at_thread_exit
    (const R& value)
  2. void set_value_at_thread_exit
    ( R&& value)
  3. void set_value_at_thread_exit
    ( R& value)
  4. void set_value_at_thread_exit()
Same as set_value() except it makes the state ready only at thread exit.
void set_exception_at_thread_exit (exception_ptr p)
Same as set_exception() except shared state is not made ready immediately. Instead, it will be made ready automatically at thread exit, once all objects of thread storage duration have been destroyed.

Example
void printresult(future<int>& result)
{
    try
    {
        cout << result.get() << endl;
    }
    catch(const exception& e)
    {
        cout << "exception caught:\t" << e.what() << endl;
    }    
}

void test(int n)
{
    promise<int> p;
    auto result = p.get_future();
    thread t([](int n,promise<int>& p)
    {
        if (n < 0)
            p.set_exception(make_exception_ptr(runtime_error("no negative number")));
        else
            p.set_value(n*2);
    }, n, ref(p));
    
    t.join();

    printresult(result);
}   

    //prints 20
    test(10);
    
   //prints:exception caught:	no negative number
    test(-10);

This example  demonstrates the functionality of the promise as seen in its console output.

This example 2 demonstrates the usage. A promise object is created and passed by reference to the thread function. The return value is set to the promise object in the thread function. After the thread function exits, a future object is created from the promise object to retrieve the shared state.

future
future object can access shared state  in the promise object containing the result or an exception. Note  the following restrictions:
  • Once the future object is retrieved from a promise object, it cannot be retrieved back. future_error is thrown. 
  • get() cannot be called on future object more than once. future_error is thrown. 
  • Accessing future object, whose promise object was not set with a value or exception will cause hang or timeout. 
  • Exceptions set in the thread functions must be accessed in a try/catch block. Otherwise it will propagate. 
syntax
template< class R > class future

//Non-void specialization, used to communicate objects between threads.
template< class R > class future<R&>

//void specialization, used to communicate stateless events.
template<> class future<void>

constructors
NameDescription
future()Default constructor. Constructs the future with an empty shared state.

methods
The following lists commonly used methods.
NameDescription
shared_future<R> share()Returns a shared_future object that acquires the shared state of the future object. This also makes this future object invalid.
  1. R get()
  2. R& future<R&>::get()
  3. void future<void>::get()
  1. Returns the result of type R
  2. Specialization for returning result of type R&
  3. Specialization for returning result of type void
It internally calls wait().
bool valid()Checks if the future has a shared state. It waits infinitely.
void wait()Waits for the result to become available.  It waits infinitely.
future_status wait_for
(duration d)
Waits for the result, returns if it is not available for the specified timeout duration. The return value future_status indicates the result of the wait.
future_status wait_until
(time_point tp)     
waits for the result, returns if it is not available until specified time point has been reached.  The return value future_status indicates the result of the wait.

The following lists the values for future_status.
NameDescription
deferredThe shared state contains a deferred function using lazy evaluation, so the result will be computed only when explicitly requested. 
readyThe result is ready
timeoutThe timeout has expired

Example
void printresult(future<int>& result)
{
    try
    {
        cout << result.get() << endl;
    }
    catch(const exception& e)
    {
        cout << "exception caught:\t" << e.what() << endl;
    }    
}

ostream& operator << (ostream& os, future_status s)
{
    if (s == future_status::deferred)
        os << "deferred";
    else if (s == future_status::ready)
        os << "ready";
    else if (s == future_status::timeout)
        os << "timeout";
    else 
        os << "unknown";   
    return os;
}

void test(launch l, int n)
{
    future<int> result = async (l, [](int n)
    {
        this_thread::sleep_for(8s);        
        if (n < 0)
           throw (runtime_error("no negative number"));
        else
            return (n*2);
    }, n);

    cout << result.wait_for(2s) << endl;
    cout << result.wait_until(steady_clock::time_point(steady_clock::now()+1s)) << endl;

    printresult(result);
} //deferred //deferred //prints 20 test(launch::deferred, 10); //deferred //deferred //prints:exception caught: no negative number test(launch::deferred,-10); //timeout //timeout //prints 20 test(launch::async, 10); //timeout //timeout //prints:exception caught: no negative number test(launch::async,-10);

This example 3  demonstrates the functionality of the future and shared_future as seen in its console output.

shared_future
It's similar to future object except  multiple threads are allowed to wait for the same shared state. It's also  copyable and multiple shared future objects may refer to the same shared state.
Access to the same shared state from multiple threads is safe if each thread does it through its own copy of a shared_future object.

syntax
template< class R > class shared_future

//Non-void specialization, used to communicate objects between threads.
template< class R > class shared_future<R&>

//void specialization, used to communicate stateless events.
template<> class shared_uture<void>


constructors
NameDescription
  1. shared_future()
  2. shared_future
    ( const shared_future& other)
  3. shared_future
    (future<T>&& other)
  1. Default constructor. Constructs the future with an empty shared state.
  2. Copy Constructor
  3. Move constructor

methods
The following lists commonly used methods.
NameDescription
  1. R get()
  2. R& future<R&>::get()
  3. void future<void>::get()
  1. Returns the result of type R
  2. Specialization for returning result of type R&
  3. Specialization for returning result of type void
It internally calls wait().
bool valid()Checks if the future has a shared state. It waits infinitely.
void wait()Waits for the result to become available.  It waits infinitely.
future_status wait_for
(duration d)
Waits for the result, returns if it is not available for the specified timeout duration. The return value future_status indicates the result of the wait.
future_status wait_until
(time_point tp)     
waits for the result, returns if it is not available until specified time point has been reached.  The return value future_status indicates the result of the wait.

Example
void printresult(const shared_future<int>& result)
{
    try
    {
        cout << result.get() << endl;
    }
    catch(const exception& e)
    {
        cout << "exception caught:\t" << e.what() << endl;
    }    
}

void test(int n)
{
    future<int> result = async ([](int n)
    {
        this_thread::sleep_for(8s);        
        if (n < 0)
           throw (runtime_error("no negative number"));
        else
            return (n*2);
    }, n);

    auto sr = result.share();
    thread t(printresult,sr);
    thread t2(printresult,shared_future<int>(sr));

    t.join();
    t2.join();

}   

int main()
{

    //prints 20
    //prints 20
    test(10);
    
   //prints:exception caught:	no negative number
   //prints:exception caught:	no negative number
    test(-10);

This example 4 demonstrates the usage. Two threads are independently launched. Before completion they wait on a  shared future object, which is set by the main thread.

No comments:

Post a Comment