yandreiy
yandreiy

Reputation: 191

std::async guarantee to run after return statement

What are some ways to execute a function asynchronously, but to have the guarantee that it will execute after returning from current function?

Example:

   std::future<void> future;
   int do_smth( std::function<void()> callback) 
   { 
       int id = 0;

       auto cb = [=](){ callback(id);};
       future = std::async(std::launch::async, cb);

       return id;
   }

The std::async call should execute after return was finished. ASFAIK that it's not guaranteed to happen, the execution might still happen before function returned.

[Edit] I want this so I can implement the observer pattern, like this:

class A
{
public:
    void send_request()
    {
        m_id = do_smth( std::bind(&A::on_result,this,_1);
    }

   void on_result(RequestId id) 
   {
      if (id == m_id) {
          // my request finished!
      }
   }
private:
   RequestId m_id;
};

I know I can use a thread, with a queue, and post events but was thinking there might something simpler, if I could use the std::async.

Upvotes: 3

Views: 2075

Answers (1)

Jonathan Wakely
Jonathan Wakely

Reputation: 171433

Your example will block at the std::async call and wait for the async task to finish (so it is entirely synchronous).

To allow the task to continue running you need to store the returned future in a variable that outlives the function, e.g. by returning it from the function, or assigning it to some long-lived object outside the function:

std::future<void> some_global_future;

int do_smth( std::function<void()> callback) 
{ 
  int id = 0;

  some_global_future = std::async(std::launch::async, callback, id);

  return id;
}

Note there is no need to create a lambda as in your example. You can pass the callback and its argument directly to std::async, and they will be copied and passed to the new thread, equivalent to the [=] capture you used for the lambda.

Since you don't appear to use the future returned by std::async another option is to use a detached thread:

int do_smth( std::function<void()> callback) 
{ 
  int id = 0;

  std::thread(callback, id).detach();

  return id;
}

Update: to prevent the new thread running until the caller has exited you would need some synchronisation e.g.

std::mutex mx;

int do_smth( std::function<void()> callback) 
{ 
  int id = 0;
  std::lock_guard<std::mutex> lock(mx);
  auto cb = [=]{ std::lock_guard<std::mutex> l(mx); callback(id); };
  std::thread(cb).detach();

  return id;
}

This ensures that callback(id) won't happen until the mutex can be locked, which means do_smth must have returned.

However, that does not mean that the assignment to m_id will have completed:

    m_id = do_smth( std::bind(&A::on_result,this,_1);

The OS scheduler could run do_smth then when it returns unlock the mutex, run the asynchronous thread, then when that finishes continue running the original thread and assign to m_id.

You will have a race condition here due to one thread assigning to m_id while the thread running the callback reads it. You need to set m_id inside do_smth to fix that.

Upvotes: 2

Related Questions