Используя Boost.Asio, возможно ли добавить обработчик, который выполняется один раз за итерацию цикла событий, если нет событий ввода-вывода? - PullRequest
0 голосов
/ 27 октября 2018

В настоящее время я пишу простой сервер, используя Boost.Asio 1.68, и мне интересно, есть ли способ добавить обработчик, который выполняется, когда цикл обработки событий не выполняет никаких других действий.

В настоящее время у меня есть это:

void completionHandler (boost::asio::io_context* ioCtx){
  // poll for some condition
  // if (condition) do some work;
  ioCtx->post(boost::bind(completionHandler, ioCtx));
}

//elsewhere
ioCtx->post(boost::bind(completionHandler, ioCtx));

Однако это не совсем то, что я хочу сделать.

1 Ответ

0 голосов
/ 28 октября 2018

Это не будет делать то, что вы ожидаете.

Например, с одним циклом async_accept вы бы никогда не достигли точки "никакой другой работы дляdo ".

Аналогично, если только одна сторона владеет выдающимися work<> ( документами и почему ), ситуация никогда не возникнетгде «нет никакой другой работы».

По сути, вы действительно хотите связать опрос:

template <typename Condition, typename Handler, typename Executor>
void run_when(Executor& ex, Condition c, Handler h) {
    struct Op {
        Executor& _executor;
        Condition _cond;
        Handler _handler;

        Op(Executor& ex, Condition c, Handler h) 
            : _executor(ex), _cond(std::move(c)), _handler(std::move(h))
        { }

        void operator()() const {
            if (_cond())
                _handler(error_code{});
            else
                ba::post(_executor, std::move(*this));
        }
    };

    ba::post(ex, Op{ex, std::move(c), std::move(h)});
}

Это можно использовать как:

run_when(io,
        [&] { return bool(triggered); },
        [](error_code ec) { std::clog << "triggered: " << ec.message() << "\n"; });

Демо

Live On Coliru

#include <boost/asio.hpp>
namespace ba = boost::asio;
using boost::system::error_code;
using namespace std::chrono_literals;

template <typename Condition, typename Handler, typename Executor>
void run_when(Executor& ex, Condition c, Handler h) {
    struct RunWhen {
        Executor& _executor;
        Condition _cond;
        Handler _handler;

        RunWhen(Executor& ex, Condition c, Handler h) 
            : _executor(ex), _cond(std::move(c)), _handler(std::move(h))
        { }

        void operator()() const {
            if (_cond())
                _handler(error_code{});
            else
                ba::post(_executor, std::move(*this));
        }
    };

    ba::post(ex, RunWhen{ex, std::move(c), std::move(h)});
}

#include <iostream>
int main() {
    // some state that gets changed in the background
    std::atomic_bool triggered { false };
    std::thread([&] { 
            std::this_thread::sleep_for(1.5s);
            triggered = true;
        }).detach();

    ba::io_context io;

    // just some background polling that shall not block other work
    run_when(io, [&] { return bool(triggered); }, [](error_code ec) { std::clog << "triggered: " << ec.message() << "\n"; });

    io.run_for(3s);
}

Печать (через ~ 1,5 с):

triggered: Success

БОНУС

Почему наш обработчик принимает код ошибки?Ну, в соответствии с другими операциями Asio, вы можете отменить их.Либо вы берете на себя ответственность за продление срока действия экземпляра run_when<>(...)::Op, что усложняет жизнь.Или вы можете сделать так, чтобы вызываемый Condition мог вернуть код, указывающий, было ли выполнено условие или было отменено ожидание¹:

Live On Coliru

#include <boost/asio.hpp>
namespace ba = boost::asio;
using boost::system::error_code;
using boost::system::system_error;
using ba::error::operation_aborted;
using namespace std::chrono_literals;

template <typename Condition, typename Handler, typename Executor>
void run_when(Executor& ex, Condition c, Handler h) {
    struct Op {
        Executor& _executor;
        Condition _cond;
        Handler _handler;

        Op(Executor& ex, Condition c, Handler h) 
            : _executor(ex), _cond(std::move(c)), _handler(std::move(h))
        { }

        void operator()() const {
            try {
                if (_cond())
                    _handler(error_code{});
                else
                    ba::post(_executor, std::move(*this));
            } catch(system_error const& se) {
                _handler(se.code());
            }
        }
    };

    ba::post(ex, Op{ex, std::move(c), std::move(h)});
}

#include <random>
auto random_delay() {
    static std::mt19937 engine(std::random_device{}());
    return (std::uniform_int_distribution<>(1,2)(engine)) * 1s;
}

#include <iostream>
int main() {
    // some state that gets changed in the background
    std::atomic_bool triggered { false }, canceled { false };
    std::thread([&] { std::this_thread::sleep_for(1.5s); triggered = true; }).detach();

    // add a randomized cancellation
    {
        auto cancel_time = random_delay();
        std::clog << "hammer time: " << (cancel_time/1.0s) << "s\n";
        std::thread([&] { std::this_thread::sleep_for(cancel_time); canceled = true; }).detach();
    }

    ba::io_context io;

    // just some background polling that shall not block other work
    auto condition = [&] { return canceled? throw system_error(operation_aborted) : bool(triggered); };

    run_when(io, condition, [](error_code ec) { std::clog << "handler: " << ec.message() << "\n"; });

    io.run_for(3s);
}

Который печатает

hammer time: 1s
handler: Success

или

hammer time: 2s
handler: Success

в зависимости от значения random_delay().


¹ (или что дочь мэра развелась, потому что код ошибки довольно универсален)

...