asio 如何更改可等待的执行程序?

asio How to change the executor inside an awaitable?

我已经阅读了这个 and tried to replicate the ,代码如下:

#include <iostream>
#include <syncstream>
#include <thread>
#include <coroutine>
#include <boost/asio.hpp>
#include <boost/asio/experimental/as_single.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread/thread.hpp>

inline std::osyncstream tout() {
  auto hash = std::hash<std::thread::id>{}(std::this_thread::get_id());
  return std::osyncstream(std::cout) << "T" << hash << " ";
}

namespace asio = boost::asio;

asio::awaitable<void> mainCo(asio::io_context &appIO, asio::io_context &prodIO) {
  // the thread should also change when using the IO contexts directly.
  auto astrand = asio::io_context::strand{appIO};
  auto pstrand = asio::io_context::strand{prodIO};

  tout() << "MC on APPIO" << std::endl;
  co_await asio::post(pstrand, asio::use_awaitable);
  tout() << "MC on PRODIO" << std::endl;
  co_await asio::post(astrand, asio::use_awaitable);
  tout() << "MC on APPIO" << std::endl;
  co_await asio::post(pstrand, asio::use_awaitable);
  tout() << "MC on PRODIO" << std::endl;
  co_await asio::post(pstrand, asio::use_awaitable); // nop - no operation because we are already on the correct execution_context
  tout() << "MC on PRODIO" << std::endl;
  co_await asio::post(astrand, asio::use_awaitable);
  tout() << "MC on APPIO" << std::endl;
}

int main() {
  asio::io_context prodIO;
  boost::thread prodThread;
  {
    // ensure the producer io context doesn't exit
    auto prodWork = asio::make_work_guard(prodIO);

    prodThread = boost::thread{[&prodIO] {
      tout() << "ProdThread run start" << std::endl;
      prodIO.run(); // if this call is removed the mainCo is stuck as expected
      tout() << "ProdThread run done" << std::endl;
    }};
    asio::io_context appIO;

    asio::co_spawn(appIO, mainCo(appIO, prodIO), asio::detached);

    tout() << "MainThread run start" << std::endl;
    appIO.run();
    tout() << "MainThread run done" << std::endl;
  }
  prodThread.join();
  return 42;
}

当前输出:

/tmp/tmp.wz38MWkttM/cmake-build-debug-remote-host/CoroContextSwitching
T14386720116392206644 MainThread run start
T8726023523478668610 ProdThread run start
T14386720116392206644 MC on APPIO
T14386720116392206644 MC on PRODIO
T14386720116392206644 MC on APPIO
T14386720116392206644 MC on PRODIO
T14386720116392206644 MC on PRODIO
T14386720116392206644 MC on APPIO
T14386720116392206644 MainThread run done
T8726023523478668610 ProdThread run done

Process finished with exit code 42

预期输出:

/tmp/tmp.wz38MWkttM/cmake-build-debug-remote-host/CoroContextSwitching
T14386720116392206644 MainThread run start
T8726023523478668610 ProdThread run start
T14386720116392206644 MC on APPIO
T8726023523478668610 MC on PRODIO
T14386720116392206644 MC on APPIO
T8726023523478668610 MC on PRODIO
T8726023523478668610 MC on PRODIO
T14386720116392206644 MC on APPIO
T14386720116392206644 MainThread run done
T8726023523478668610 ProdThread run done

Process finished with exit code 42

我希望线程 ID 会根据 cout 语句发生变化。但是所有的 cout 语句都在 MainThread 上执行。

如何获得所需的行为?

编辑 2:

原来的问题仍然成立,只是为问题添加了更多信息。

似乎 asio::use_awaitable 从某处绑定了默认执行程序。 是否有记录的默认值?

通过以下编辑函数我可以实现我想要的:

asio::awaitable<void> mainCo(asio::io_context &appIO, asio::io_context &prodIO) {
  // the thread should also change when using the IO contexts directly.
  auto astrand = asio::io_context::strand{appIO};
  auto pstrand = asio::io_context::strand{prodIO};

  tout() << "MC on APPIO" << std::endl;
  co_await asio::post(pstrand, asio::bind_executor(pstrand, asio::use_awaitable)); // so use_awaitable is binding a default executor from somewhere.
  tout() << "MC on PRODIO" << std::endl;
  co_await asio::post(astrand, asio::use_awaitable);
  tout() << "MC on APPIO" << std::endl;
  co_await asio::post(pstrand, asio::bind_executor(pstrand, asio::use_awaitable));
  tout() << "MC on PRODIO" << std::endl;
  co_await asio::post(pstrand, asio::bind_executor(pstrand, asio::use_awaitable)); // nop - no operation because we are already on the correct execution_context
  tout() << "MC on PRODIO" << std::endl;
  co_await asio::post(astrand, asio::use_awaitable);
  tout() << "MC on APPIO" << std::endl;
  co_await asio::post(astrand, /* the first parameter in post doesn't even matter (can be astrand, pstrand, appIO, prodIO) same result */
                      asio::bind_executor(pstrand, asio::use_awaitable));
  tout() << "MC on PRODIO" << std::endl;
  co_await asio::post(astrand, asio::use_awaitable);
  tout() << "MC on APPIO" << std::endl;
}

但是,这似乎不是切换执行程序的正确方法,因为 asio::post 的第一个参数无关紧要。那么正确的做法是什么?

编辑: 问题已关闭,并向我指出了这个 。我知道 co_spawn 不会生成新线程。这就是为什么我自己生成一个名为 prodThread 的新线程。我希望 execution_context 在等待 post 语句后切换。链接的问题没有回答我的问题。

您想将您的执行者与完成令牌相关联,然后让post/dispatch/defer从那里找出答案:

co_await asio::post(bind_executor(pstrand, asio::use_awaitable));

See also e.g. or for more details on how it works/why it works/when it works.

It does in fact explain how get_associated_executor does use a default so it might explain how explicitly posting to an executor didn't seem to work here (I haven't checked the associated executor implementation for use_awaitable just now)

这是我的看法:

Live On Compiler Explorer

#include <boost/asio.hpp>
#include <boost/asio/experimental/as_single.hpp>
#include <boost/bind/bind.hpp>
#include <coroutine>
#include <iostream>
#include <thread>

inline void tout(auto const& msg) {
    static std::mutex mx;
    std::lock_guard   lk(mx);

    static const std::hash<std::thread::id> h{};

    std::cout << "T" << (h(std::this_thread::get_id()) % 100) << " " << msg
            << std::endl;
}

namespace asio = boost::asio;

asio::awaitable<void> mainCo(asio::io_context& appIO,
                            asio::io_context& prodIO) {
    auto to_app = bind_executor(make_strand(appIO), asio::use_awaitable);
    auto to_prod = bind_executor(make_strand(prodIO), asio::use_awaitable);

    tout("MC on APPIO");
    co_await asio::post(to_prod); tout("MC on PRODIO");
    co_await asio::post(to_app);  tout("MC on APPIO");
    co_await asio::post(to_prod); tout("MC on PRODIO");
    co_await asio::post(to_prod); tout("MC on PRODIO");
    co_await asio::post(to_app);  tout("MC on APPIO");
}

int main() {
    asio::io_context prodIO, appIO;
    auto             prodWork = asio::make_work_guard(prodIO);

    std::thread prodThread{[&prodIO] {
        tout("ProdThread run start");
        prodIO.run(); // if this call is removed the mainCo is stuck as
                    // expected
        tout("ProdThread run done");
    }};

    asio::co_spawn(appIO, mainCo(appIO, prodIO), asio::detached);

    tout("MainThread run start");
    appIO.run();
    tout("MainThread run done");

    prodWork.reset();
    prodThread.join();
}

打印例如

T49 ProdThread run start
T31 MainThread run start
T31 MC on APPIO
T49 MC on PRODIO
T31 MC on APPIO
T49 MC on PRODIO
T49 MC on PRODIO
T31 MC on APPIO
T31 MainThread run done
T49 ProdThread run done

奖金

我建议传递执行程序,而不是执行上下文引用。它更干净、更灵活:https://godbolt.org/z/Tr54vf8PM

然后用简单的 thread_pool 执行上下文替换 prodIO + thread 变得微不足道。它消除了对工作守卫的需要,还修复了缺少的异常处理:https://godbolt.org/z/a3GT61qdh