C++ Async Development (not only for) for C# Developers Part III: Introduction to C++ async

Now that we know a bit about lambda functions in C++ we finally can take a look at C++ async programming with the C++ Rest SDK (a.k.a. cpprestsdk a.k.a. Casablanca). C++ Rest SDK is cross platform but thanks to the NuGet support for native packages using it with Visual Studio is extremely easy. If you would like to use it on non-Windows platforms in majority of cases you will have to compile the code yourself (note to Raspbery PI fans – I tried compiling it on my Raspberry Pi but unfortunately compilation failed due to a gcc internal compiler error). With Visual Studio you can create a new Win32 Console Application, right click on the project in the Solution Explorer and click the “Manage NuGet Packages” option. In the Manage NuGet Packages window type “cppresdk” in the in the search box and install the C++ REST SDK: C++ Rest SDK NuGet This is it – you are now ready to write asynchronous programs in C++. If you prefer, you can also install the package from the Package Manager Console with the Install-Package cpprestsdk command. Once the C++ Rest SDK is installed we can start playing with it. Let’s start with something really simple like displaying a textual progress bar. We will create a task that will loop 10 times. In each iteration it will sleep for a short period of time and then write an asterisk to the console. To show that it works asynchronously we will have a similar loop in the main thread where we similarly will wait and write a different character to the console. I expect the characters to be mixed which would prove that the progress task is actually running on a different thread. Here is the code:

#include "stdafx.h"
#include "pplx\pplxtasks.h"
#include <iostream>

int main()
{
    pplx::task<void>([]()
    {
        for (int i = 0; i < 10; i++)
        {
            pplx::wait(200);
            std::cout << "*";
        };

        std::cout << std::endl << "Completed" << std::endl;
    });

    for (int i = 0; i < 10; i++)
    {
        pplx::wait(300);
        std::cout << "#";
    }

    std::cout << std::endl;

    return 0;
}

And here is the result I got:

*#**#*#**#*#**#*
Completed
####
Press any key to continue . . .

From the output it appears that everything went as planned. Let’s take a closer look at what is really happening in the program. pplx::task<void> creates a new task that is scheduled to be run on a different thread. Once the task is created the loop writing # characters is being executed. In the meantime the scheduled task is being picked up and executed on a different thread. Note that the loop in the main thread will take longer to execute that the loop in the task. What would happen if the main thread did not live long enough – e.g. we would not have the loop in the main thread that runs longer than the task? You can easily check this by decreasing the timeout but basically the main thread would exit and would terminate all other threads – including the one the task is running on so the task would be killed. You can, however, block a thread and wait until a task is completed by using the .get() or the .wait() method. In general you want to avoid blocking threads but sometimes it can be helpful. (Note that this is in contrast to the managed world (e.g. C#) where the expectation is that apps using async features are async inside out and blocking oftentimes leads to bad things like deadlocks. The built-in support for async like async/await keywords and exception handling in async methods help tremendously meet this expectation.) Here is a new version of the program from above which is now using the .get() method to block execution of the main funcion until the task completes:

int main()
{
    auto task = pplx::task<void>([]()
    {
        for (int i = 0; i < 10; i++)
        {
            pplx::wait(200);
            std::cout << "*";
        };

        std::cout << std::endl << "Completed" << std::endl;
    });

    std::cout << "waiting for task to complete" << std::endl;
    task.get();
    std::cout << "task completed" << std::endl;

    return 0;
}

The program should output this:

waiting for task to complete
**********
Completed
task completed
Press any key to continue . . .

The output shows that .get() did the job – once the .get() method was reached the main thread was blocked waiting for the task to complete and then when the task finished the main thread was unblocked. This is great but can we take it to the next level – for instance – can we return a value from the task? This is actually quite easy – to do that you just need to return the value from the task. In our case we can return how much time (in nanoseconds) our task took to execute. We will use types from the std::chrono namespace so don’t forget to add #include <chrono> – I am leaving includes out for brevity.

int main()
{
    auto task = pplx::task<std::chrono::nanoseconds>([]()
    {
        auto start = std::chrono::steady_clock::now();

        for (int i = 0; i < 10; i++)
        {
            pplx::wait(200);
            std::cout << "*";
        };
        
        std::cout << std::endl << "Completed" << std::endl;

        return std::chrono::steady_clock::now() - start;
    });

    std::cout << "waiting for task to complete" << std::endl;
    auto task_duration = task.get();
    std::cout << "task completed in " << task_duration.count() << " ns." << std::endl;

    return 0;
}

If you look close at the code you will notice that I modified the type of the task – instead of being pplx::task<void> it is now pplx::task<std::chrono::nanoseconds> – and modified the lambda body so that it now returns a value. As a result the .get() method is no longer void – it now returns a value of the std::chrono::nanoseconds type which is actually the value we returned from our task. For completness this is what I got on my screen when I ran this program:

waiting for task to complete
**********
Completed
task completed in 2003210000 ns.
Press any key to continue . . .

While being able to run a task asynchronously is powerful oftentimes you would want to run another task that runs after a task has completed and that uses the result from the previous task. Both tasks should run asynchronously and should not require blocking the main thread to pass the result from one task to the other task. For instance you are working with a service that returns a list of ids and names in one request but also can return details for a given id in a different request. If you want to get details for a given name you would need to first send a request to get ids for names and then send another request to get the details for the id. From the perspective of the main thread you just want to say: “give me details for this name (and I don’t care how you do it)”. This can be achieved with task chaining. You chain tasks using the .then() method. In the simplest form it just takes the value returned by the previous task as the parameter. For example, in our case, if we wanted to get the result in milliseconds and not nanoseconds we could write a continuation that does the conversion (yes, there is no real benefit of doing such a simple conversion in a continuation especially that it isn’t an asynchronous operation and can easily be done in the first continuation or in the main thread but imagine you need to connect to a service that does the conversion for you) like this:

int main()
{
    auto task = pplx::task<std::chrono::nanoseconds>([]()
    {
        auto start = std::chrono::steady_clock::now();

        for (int i = 0; i < 10; i++)
        {
            pplx::wait(200);
            std::cout << "*";
        };

        std::cout << std::endl << "Completed" << std::endl;

        return std::chrono::steady_clock::now() - start;
    })
    .then([](std::chrono::nanoseconds duration)
    {
        std::cout << "task duration in ns: " << duration.count() << std::endl;
        return duration.count() / 1000000;
    });

    std::cout << "waiting for task to complete" << std::endl;
    auto task_duration = task.get();
    std::cout << "task completed in " << task_duration << " ms." << std::endl;

    return 0;
}

Running the program results in the following output:

waiting for task to complete
**********
Completed
task duration in ns: 2004372000
task completed in 2004 ms.
Press any key to continue . . .

That’s pretty much it for today. Next time we will look at different types of continuations, exception handling and possibly cancellation.

2 thoughts on “C++ Async Development (not only for) for C# Developers Part III: Introduction to C++ async

Leave a comment