3

Is it possible to know when all pending asynchronous tasks in strand are complete? Just like thread.join() does in the following example:

io_service service;
std::thread thread([&](){ service.run(); });

service.post(some_function);
service.post(another_function);

service.stop();
thread.join();

It would be useful for efficient execution of multiple tasks on multiple threads. Each task is more complex than ordinary function and has it's own strand. But I've found no way to wait for strand until it has 'work'. I've tried to post to a strand finalizing handler in hope it would be called last, but the order of handlers in strand is undefined, so it fires immediately.

The code with strand would be:

io_service service;
strand<io_context::executor_type> strand(make_strand(service));
std::thread thread([&](){ service.run(); });

post(strand, some_function);
post(strand, another_function);

// here we want to wait for strand to complete pending tasks

// somewhere else later
service.stop();
thread.join();

Thank you.

san
  • 443
  • 5
  • 12

3 Answers3

2

One way to achieve this is to think of it the other way around:

  • post the work to the executor
  • run the executor until all work is done.

Example:

#include <iostream>
#include <boost/asio.hpp>
#include <thread>
#include <utility>

void some_function()
{
    std::cout << __func__ << std::endl;
}

void another_function()
{
    std::cout << __func__ << std::endl;
}

namespace {

    using namespace boost::asio;

    void test(executor exec)
    {
        post(exec, some_function);
        post(exec, another_function);
    }
}

int main()
{
    auto ioc = boost::asio::io_context();
    auto strand = boost::asio::make_strand(ioc.get_executor());

    // post the work before starting the thread
    test(strand);

    // now run the io_context
    auto t = std::thread([&](){ ioc.run(); });


    // wait for work to finish

    if (t.joinable())
        t.join();

    // thread's io_context has run out of work
}

Another way is to use a strand on a system_executor.

System executors perform work on a pool of background threads (if you post to them) so you'll need to provide your own signal that the work is complete:

#include <iostream>
#include <boost/asio.hpp>
#include <future>
#include <utility>

void some_function()
{
    std::cout << __func__ << std::endl;
}

void another_function()
{
    std::cout << __func__ << std::endl;
}

namespace {

    using namespace boost::asio;

    // note: a strand models an executor
    auto test(executor exec) -> std::future<void>
    {
        post(exec, some_function);
        post(exec, another_function);

        auto p = std::promise<void>();
        auto f = p.get_future();
        post(exec, [p = std::move(p)]() mutable {
            p.set_value();
        });
        return f;
    }
}

int main()
{
    // note: strand created here
    auto strand = boost::asio::make_strand(boost::asio::system_executor());

    auto f = test(strand);

    // wait for work to finish

    f.get();

    // thread's work is done
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • The first obvious one is equivalent of just using thread.join(), but, unfortunately it's not possible since thread pool is running all the time. The second one looks promising, but it seems that I need to dig more to learn about modern asio features :) BTW, the idea was to use strands as replacement for io_services, but a lot of them, and to distribute workload over all available threads. Previous implementation used round-robin strategy with N io_services, each running it's own thread. This approach was not so efficient - some threads was overloaded while others has too much idle cpu time – san Feb 06 '20 at 18:42
  • The correct approach of course depends on the real use case. If you want background workers, system_executor is probably the most reasonable place to post your tasks. – Richard Hodges Feb 06 '20 at 18:52
  • If you don’t need the tasks to synchronize with each other, there’s no need for the strand. – Richard Hodges Feb 06 '20 at 18:52
  • Each task is a set of functions, which should be synchronized within it's task, that's why I'm thinking about a strand per one task. – san Feb 06 '20 at 19:05
  • 1
    That sounds reasonable. Or each function within a task could take responsibility for posting the next function invocation. – Richard Hodges Feb 06 '20 at 19:18
1

You can post a use_future "completion token" to the strand. This "token" will be executed only after all other handlers in the strand have completed.

You can then wait/get on the future.

std::future<void> wait=boost::asio::post(strand, boost::asio::use_future);
wait.get();

wait.get() will succeed after all handlers have been executed.

This is also a nice way to insert "synchronisation points". Nothing prevents you from posting more handlers after this. So you can

  • post handler
  • post handler
  • post future1
  • post handler
  • post future2

and wait on the futures.

BTW, I don't know if this works with boost::asio 1.72 yet, but it will work with newer boosts. boost::asio::use_future and completion tokens are a newer addition.

Hajo Kirchhoff
  • 1,969
  • 8
  • 17
0

if in coroutines function ,you can simply use like this:

asio::awaitable<void> func(){
    asio::io_context::strand strand(io_context);
    asio::post(strand, handle1);
    asio::post(strand, handle2);
    co_await asio::post(asio::bind_executor(strand, asio::use_awaitable));
}

yue W.
  • 1
  • 1