Skip to content

3.5 Waiting for completion

Alexander Damian edited this page Aug 21, 2019 · 3 revisions

Waiting on single futures or groups of futures

Waiting on a coroutine or asynchronous IO task to complete is as simple as fetching its future value.

ThreadContextPtr<int> tctx = dispatcher.post([](CoroContextPtr<int> ctx)
{
    ...
    return ctx->set(5); //set the promise
});

//Main thread blocks until the coroutine is over
tctx->wait();

//or get the value directly
int value = tctx->get(); 

NOTE: If both wait() and get() are called sequentially, the wait() will block but not the get().

If we want to wait on specific coroutines to end before continuing, we can use the following pattern:

std::vector<IThreadContextBasePtr> waiters;

//suppose we want to wait on coroutines 2 and 4
dispatcher.post(func1);
waiters.push_back(dispatcher.post(func2));
dispatcher.post(func3);
waiters.push_back(dispatcher.post(func4));

//Loop and wait
for (auto&& ctx : waiters) ctx->wait();

//Once we exit the loop, we are guaranteed that func 2 and 4 futures are ready. 
//Func1 and func3 could still be running. 

NOTE: Using wait(), get() and their waitAt() or getAt() counterparts does not necessarily imply that those coroutines or IO tasks are completely over. It just means the futures are ready. If a coroutine sets a promise early and then goes on to do other tasks before returning, it may well run beyond the call to wait() or get(). If however the promise is set at the end, then the calling thread is perfectly synchronized with the coroutine exit.

Waiting on an a continuation chain

Waiting for an entire continuation chain can be done with the help of waitAll():

ThreadContext<double> tctx = dispatcher.postFirst(func1)->then(func2)->finally(func3)->end();

//blocks until all futures are ready
tctx->waitAll();

Waiting on the dispatcher

Sometimes we just need to wait for all the queued operations (coroutines and IO tasks alike) to complete so that we can gracefully shutdown or perhaps run another series of operations which depend on the successful completion of the first series. If the number of coroutines is very large, it becomes cumbersome to use the above pattern with the waiter queue. Instead, the dispatcher object has a drain() method, which blocks until all coroutine and IO queues are empty.

//Post 20K tasks
for (int i = 0; i < 10000; ++i)
{
    dispatcher.post(calculateSomething);
    dispatcher.postAsyncIo(printSomething);
}

//Wait for completion
dispatcher.drain();

dispatcher.post(calculateSomethingElse); //OK to post again

NOTE: One thing to consider is that while the dispatcher is draining, it is not permitted to post additional tasks, unless they are posted from within the currently running tasks.