Tuesday, June 24, 2014

Informative Status Update From std::future<>

std::future provides amazing functionality and really allows programs to take full advantage of the possibility of parallelism. Coupled with std::async, launching tasks into the asynchronous world has never been easier! Whenever we make a function call to std::async, we get back a std::future, no surprise here. However, it is sometimes good to know if our task is done, and keep our users posted on the status of our task, with some sort of notification, be it command line / ASCII art or if we're working with GUI's we can display a task bar.




Luckily for us, std::future provides a few more function calls that just a .get()/.wait() ;) . The std::future class presents us with functions that allow us to query the status of our task, as well as wait for a specified timeout threshold. The return type for these functions is defined as a std::future_status, whose enum class is depicted below:



enum class future_status
{
    ready,
    timeout,
    deferred
};

The variable names are pretty intuitive after some study:



  • ready means the task finished successfully and under given time constraints, if any were specified
  • timeout means that we waited for the thread to finish for a certain amount of time, but didn't finish.
  • deferred means this task is a deferred function, so it will only be executed if explicitly requested. (Read more about it on the docs).

Let's say we wanted to inform the user that the task is still currently running by displaying '.' (dots) on the command line, with a timeout of 1 minute (60 seconds). We can accomplish this by constantly checking if the task at hand is done by  calling wait_for() on the task for  a small amount of time (10 ms or so), or even instantaneous (0 seconds) and seeing if the return value of wait_for() is std::future_status::ready.  Let's write some code :)

// void for simplicity, but this applies to any return type
 std::future <void> f = std::async(std::launch::async, MyFunc, params); 

 auto because = std::async(std::launch::async,[&]() 
         {
           // here we wait for 0 seconds and try it again, 
           // but we can change this 100ms or 50ms or any arbitrary waiting time
           while(f.wait_for(std::chrono::seconds(0)) != std::future_status::ready)
               std::cout << ".";
         }).wait_for(std::chrono::seconds(60));

 if(because == std::future_status::ready) 
   std::cout << "Successfully Completed\n";
 else
   std::cout << "Timeout";
So here we are just launching a thread to do the work specified by MyFunc, and associated parameters, which we capture in a future. Then we launch another thread whose only job is to keep querying the task f and checking if it has finished. Since this new thread could possibly run forever, we call wait_for() on it and cap it at 60 seconds, so that the querying + outputting only runs for at most 60 seconds. Then we check our return value,and output correspondingly.Of course this code blocks on the thread which updates the user -- which is certainly NOT okay to our parallel and concurrency morals ;) So a workaround would be to make a separate call to async() with the work to do be a lambda which essentially does the auto because = ... routine and displays the results when finished. This way we monitor the task in parallel so we can work on other things while the GUI stuff finishes. In conclusion, our final code would look something like this:

 std::future &ltvoid&gt f = std::async(std::launch::async, MyFunc, params);

 auto update_gui = std::async(std::launch::async,[&]()
         {

                 auto because = std::async(std::launch::async,[&]() 
                 {
                     
                    while(f.wait_for(std::chrono::seconds(0)) != std::future_status::ready)
                          std::cout << ".";
                 }).wait_for(std::chrono::seconds(60));

                if(because == std::future_status::ready) 
                    std::cout << "Successfully Completed\n";
                else
                    std::cout << "Timeout";


         });
 // continue with Non-GUI work here so as to not interfere with updates.

I hope you have found this informative! Let me know what you think :)

No comments:

Post a Comment