Promise and Async in C++

In the past couple days I have been trying to get up at 06:00 AM instead of 05:00 AM. It is not working yet. Today I woke up around 04:30 AM, checked the time in my phone and decided to get up. Read a few articles of which two called my attention the most.

In Israel, Intel’s Mobileye has been testing an autonomous car that makes use of 12 cameras. Their software has ability to connect to RADAR and LIDAR but they have been driving the car in the city of Jerusalem. The video shown at CES show in Las Vegas looks pretty good. I agree with Elon Musk’s idea that an autonomous car should not require LIDAR. That said how many cameras and their position would be critical for success. I believe most humans only use their two eyes (one stereo camera) to drive vehicles. Of course humans have a very powerful computer which at this time we know little about how it works. Given time, I believe autonomous cars will use a limited set of cameras and newer and more powerful algorithms to allow them drive in most traffic and road conditions.

The other article that called my attention deals with Gluon and AutoGluon. The idea is to be able to train a ML model with three lines of code. AutoGluon is the result of work by Amazon and Microsoft. Later this week I will take a look at Cognitive Toolkit from Microsoft to learn more about it.

In this post I will be experimenting with Future and Promise using C++. I have a couple posts Creating Threads in C++ and Threads and Templates C++ that I used as introduction to futures and promises.

main <<< entering
main <<< waiting for thread to complete ...
threadFn <<< entering
threadFn <<< starting work ...
threadFn <<< done working
threadFn <<< bye bye
main <<< thread finished
main <<< bye bye

The screen capture of the console created by the Visual Studio 2019 Enterprise Edition seems to indicate that main created a thread and is waiting for it to complete. The threadFn thread starts and does some work (sleeps in our case). The main thread waits for the thread to complete. As soon as the threadFn completes the main thread exits. If you download and run the code you should be able to see it running and experiencing the delays here mentioned.

// **** includes ****
#include <iostream>
#include <thread>

// **** namespaces ****
using namespace std;

//
// Thread function.
//
void threadFn   (
                void
                )
{

    // **** welcome message ****
    cout << "threadFn <<< entering\n";

    // **** simulate work ****
    cout << "threadFn <<< starting work ...\n";
    this_thread::sleep_for(chrono::seconds(3));
    cout << "threadFn <<< done working\n";

    // **** exit message ****
    cout << "threadFn <<< bye bye\n";
}

//
// Test scafolding.
//
int main    (
            void
            )
{

    // **** welcome message ****
    cout << "main <<< entering\n";

    // **** create thread ****
    thread t1 { threadFn };

    // **** wait for thread to complete ****
    cout << "main <<< waiting for thread to complete ...\n";
    t1.join();
    cout << "main <<< thread finished\n";

    // **** exit message ****
    cout << "main <<< bye bye\n";

    // **** ****
    return 0;
}

The C++ code illustrates the includes needed and the fact that we used the std namespace.

The threadFn displays a message, delays for three seconds and exits the thread.

The main displays a message, creates a thread with the threadFn function and then waits for the thread to exit. Once the thread exits it displays a message that main is about to exit and it does by returning a value of zero. We have used this setup in the two previous associated posts.

// **** includes ****
#include <iostream>
//#include <thread>
#include <future>

// **** namespaces ****
using namespace std;

//
// Asynchronous function.
//
void asyncFn    (
                void
                )
{

    // **** welcome message ****
    cout << "asyncFn <<< entering threadID: " << this_thread::get_id() << endl;

    // **** do some work ****
    cout << "asyncFn <<< starting work...\n";
    this_thread::sleep_for(chrono::seconds(3));
    cout << "asyncFn <<< done working\n";

    // **** exit message ****
    cout << "asyncFn <<< bye bye\n";
}

//
// Test scafolding.
//
int main    (
            void
            )
{

    // **** welcome message ****
    cout << "main <<< entering threadID: " << this_thread::get_id() << endl;

    // **** start asynchronous function which will return a future ****
    cout << "main <<< before starting asyncFn\n";

    future<void> fn = async(launch::async, asyncFn);
    //future<void> fn = async(launch::deferred, asyncFn);

    cout << "main <<< after starting asyncFn\n";

    // **** ****
    cout << "main <<< doing some work...\n";
    this_thread::sleep_for(chrono::seconds(7));
    cout << "main <<< done with work\n";

    // **** wait for the future function to complete ****
    cout << "main <<< wait for asyncFn to complete...\n";
    fn.get();
    cout << "main <<< asyncFn completed\n";

    // **** exit message ****
    cout << "main <<< bye bye\n";

    // **** ****
    return 0;
}

In this case we replace the thread include with the future include file.

We declare a function named asyncFn that displays a message, delays for three seconds, displays a message and exits.

The main() function displays the thread ID and starts an asynchronous function. Main then sleeps for seven seconds. After the wait it waits for the asynchronous function to complete via the get() function call. It then displays a message.

main <<< entering threadID: 6540
main <<< before starting asyncFn
main <<< after starting asyncFn
main <<< doing some work...
asyncFn <<< entering threadID: 16892
asyncFn <<< starting work...
asyncFn <<< done working
asyncFn <<< bye bye
main <<< done with work
main <<< wait for asyncFn to complete...
main <<< asyncFn completed
main <<< bye bye

We can see that the main thread displays a message, starts the asyncFn, does some work (sleeps) and then waits for the asyncFn to complete. It displays a message and then exits.

   // **** start asynchronous function which will return a future ****
    cout << "main <<< before starting asyncFn\n";

    //future<void> fn = async(launch::async, asyncFn);
    future<void> fn = async(launch::deferred, asyncFn);

    cout << "main <<< after starting asyncFn\n";

We went to the main() function and commented out launch::async and replaced it with launch::deferred. The rest of the code remains the same.

main <<< entering threadID: 10032
main <<< before starting asyncFn
main <<< after starting asyncFn
main <<< doing some work...
main <<< done with work
main <<< wait for asyncFn to complete...
asyncFn <<< entering threadID: 10032
asyncFn <<< starting work...
asyncFn <<< done working
asyncFn <<< bye bye
main <<< asyncFn completed
main <<< bye bye

If you follow the code, you will see that  asyncFn() is not started until the main() function calls fn.get() to check if the function is completed.

//
// Asynchronous function.
//
int asyncFn (
            void
            )
{

    // **** welcome message ****
    cout << "asyncFn <<< entering threadID: " << this_thread::get_id() << endl;

    // **** do some work ****
    cout << "asyncFn <<< starting work...\n";
    this_thread::sleep_for(chrono::seconds(3));
    cout << "asyncFn <<< done working\n";

    // **** exit message ****
    cout << "asyncFn <<< bye bye\n";

    // **** return an arbitrary value ****
    return 123;
}

In this las modification, the asyncFn() returns an integer when the function exits.

//
// Test scafolding.
//
int main    (
            void
            )
{

    // **** welcome message ****
    cout << "main <<< entering threadID: " << this_thread::get_id() << endl;

    // **** start asynchronous function which will return a future ****
    cout << "main <<< before starting asyncFn\n";

    //future<void> fn = async(launch::async, asyncFn);
    //future<void> fn = async(launch::deferred, asyncFn);
    future<int> fn = async(launch::async, asyncFn);

    cout << "main <<< after starting asyncFn\n";

    // **** do some local work ****
    cout << "main <<< doing some work...\n";
    this_thread::sleep_for(chrono::seconds(7));
    cout << "main <<< done with work\n";

    // **** wait for the future function to complete ****
    cout << "main <<< wait for asyncFn to complete...\n";
    //fn.get();
    int value = fn.get();
    cout << "main <<< asyncFn value: " << value << " completed\n";

    //// **** calling get() a second time (with throw an exception) ****
    //fn.get();

    // **** exit message ****
    cout << "main <<< bye bye\n";

    // **** ****
    return 0;
}

We start the main() function as before but we get a value from fn.get() and display it. This value is the same value returned by the asyncFn() function. Note that we can only call fn.get() once. In a few more examples we will explore how to call it more than once.

main <<< entering threadID: 20584
main <<< before starting asyncFn
main <<< after starting asyncFn
main <<< doing some work...
asyncFn <<< entering threadID: 1620
asyncFn <<< starting work...
asyncFn <<< done working
asyncFn <<< bye bye
main <<< done with work
main <<< wait for asyncFn to complete...
main <<< asyncFn value: 123 completed
main <<< bye bye

The output illustrates that all is the same as in the previous example, with the difference that the returned value by asyncFn() is picked up by the main() thread.

int main    (
            void
            )
{

    // **** welcome message ****
    cout << "main <<< entering threadID: " << this_thread::get_id() << endl;

    // **** start asynchronous function which will return a future ****
    int value = 123;
    cout << "main <<< before starting asyncFn with value: " << value << endl;

    //future<void> fn = async(launch::async, asyncFn);
    //future<void> fn = async(launch::deferred, asyncFn);
    future<int> fn = async(launch::async, asyncFn, value);

    cout << "main <<< after starting asyncFn with value: " << value << endl;

    // **** do some local work ****
    cout << "main <<< doing some work...\n";
    this_thread::sleep_for(chrono::seconds(7));
    cout << "main <<< done with work\n";

    // **** wait for the future function to complete ****
    cout << "main <<< wait for asyncFn to complete...\n";
    //fn.get();

    // **** check if async function completed ****
    if (fn.valid()) {
        value = fn.get();
        cout << "main <<< asyncFn value: " << value << " completed line: " << __LINE__ << endl;
    }
    else {
        cout << "main <<< asyncFn invalid\n";
    }

    //// **** calling get() a second time (with throw an exception) ****
    //fn.get();

    // **** ****
    if (fn.valid()) {
        value = fn.get();
        cout << "main <<< asyncFn value: " << value << " completed\n";
    }
    else {
        cout << "main <<< asyncFn invalid line: " << __LINE__ << endl;
    }

    // **** exit message ****
    cout << "main <<< bye bye\n";

    // **** ****
    return 0;
}

In this example we attempt to call fn.get() a couple times. In the first instance we call the fn.valid() method to determine if the thread has completed. If so we display the returned value. If the thread has not exited, we get an invalid value (false).

We can call the fn.get() a second time (after the thread has completed) and our main() would throw an exception. We avoid that by checking first with the fn.valid() method.

main <<< entering threadID: 14144
main <<< before starting asyncFn with value: 123
main <<< after starting asyncFn with value: 123
main <<< doing some work...
asyncFn <<< entering threadID: 13148
asyncFn <<< starting work...
asyncFn <<< done working
asyncFn <<< bye bye
main <<< done with work
main <<< wait for asyncFn to complete...
main <<< asyncFn value: 246 completed line: 112
main <<< asyncFn invalid line: 127
main <<< bye bye

In this run we call the first time fn.get() and we get the returned value. We try calling it a second time by bracketing it with a call to fn.valid() method. In this case we display a proper message and our code does not throw an exception. To see all this in action I would recommend you coding along or just download the code form my GitHub repository.

int main    (
            void
            )
{

    // **** welcome message ****
    cout << "main <<< entering\n";

    // **** create a promise ****
    promise<int> myPromise;
    future<int> fut = myPromise.get_future();

    // **** delay for a few ****
    this_thread::sleep_for(chrono::seconds(5));

    // **** set the promise value ****
    cout << "main <<< before promise set_value()...\n";
    myPromise.set_value(123);
    cout << "main <<< after promise set_value()\n";

    // **** get promise value  ****
    cout << "main <<< before get value...\n";
    int value = fut.get();
    cout << "main <<< after get value: " << value << endl;

    // **** exit message ****
    cout << "main <<< bye bye\n";

    // **** ****
    return 0;
}

In this example we create a promise, delay execution for a few seconds, set the value of the promise to 123 and get the value from the promise. The values should match.

main <<< entering
main <<< before promise set_value()...
main <<< after promise set_value()
main <<< before get value...
main <<< after get value: 123
main <<< bye bye

The output confirms that the value obtained from the promise in main() matches the one we set in the same function a few steps before. This example shows how to use promises. We need to look at a better scenario that illustrates an actual need.

int main()
{
    
    // **** welcome message ****
    cout << "main <<< entering threadID: " << this_thread::get_id() << endl;

    // **** create a promise ****
    promise<int> myPromise;
    future<int> fut = myPromise.get_future();

    // **** create a thread to deliver the promise ****
    thread t1 { ThreadFn, ref(myPromise) };

    // **** get the value of the promise ****
    cout << "main <<< before fut.get()...\n";
    int value = fut.get();
    cout << "main <<< after fut.get() value: " << value << endl;

    // **** wait for the thread to exit ****
    cout << "main <<< wait for the thread to exit...\n";
    t1.join();
    cout << "main <<< thread is done\n";

    // **** exit message ****
    cout << "main <<< bye bye\n";

    // **** ****
    return 0;
}

In this example, we create a promise, start a thread and pass the promise as an argument. We then get the value of the promise and display it. We wait for the thread to complete and then we exit our program.

//
// Thread function that sets a promise.
// 
void ThreadFn   (
                promise<int> &myPromise
                )
{
    
    // **** welcome message ****
    cout << "ThreadFn <<< entering threadID: " << this_thread::get_id() << endl;

    // **** delay for a few ****
    cout << "ThreadFn <<< before first delay...\n";
    this_thread::sleep_for(chrono::seconds(5));
    cout << "ThreadFn <<< after first delay\n";

    // **** set the value on the promise ****
    myPromise.set_value(123);

    // **** delay for a few ****
    cout << "ThreadFn <<< before second delay...\n";
    this_thread::sleep_for(chrono::seconds(7));
    cout << "ThreadFn <<< after second delay\n";

    // **** exit message ****
    cout << "ThreadFn <<< bye bye\n";
}

The ThreadFn function takes an integer promise. It delays for a few, sets a value in the promise, delays for a few more seconds and then exits.

main <<< entering threadID: 12384
main <<< before fut.get()...
ThreadFn <<< entering threadID: 19460
ThreadFn <<< before first delay...
ThreadFn <<< after first delay
ThreadFn <<< before second delay...
main <<< after fut.get() value: 123
main <<< wait for the thread to exit...
ThreadFn <<< after second delay
ThreadFn <<< bye bye
main <<< thread is done
main <<< bye bye

In this last screen capture we see that the main() thread starts the ThreadFn. The ThreadFn delays a couple times. In between the delays it sets the promise to value 123.

The main thread gets and displays the value of the promise. It then waits for the thread to exit. It displays a message end exits.

The entire code for this project can be found in my GitHub repository.

If you have comments or questions regarding this, or any other post in this blog, or if you would like for me to serve of assistance with any phase in the SDLC (Software Development Life Cycle) of a project associated with a product or service, please do not hesitate and leave me a note below. If you prefer, send me a message using the following address:  john.canessa@gmail.com. All messages will remain private.

Keep on reading and experimenting. It is the best way to learn, refresh your knowledge and enhance your developer toolset!

John

Twitter:  @john_canessa

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.