是的,你确实错过了一些东西。实际上有两件事。
类型擦除
绑定执行器不会修改函数,而是修改其类型。
但是,通过使用擦除可调用的类型std::function<>
你隐藏了绑定的执行者。您可以轻松确定这一点:
erased_handler handle = bind_executor(s2, s2_handler);
assert(asio::get_associated_executor(handle, s1) == s1);
当您保留类型时,问题就消失了:
auto handle = bind_executor(s2, s2_handler);
assert(asio::get_associated_executor(handle, s1) == s2);
派遣(以前handler_invoke
)
调用handle
正如您所发现的,直接根据 C++ 语言语义调用它。
要要求 Asio 尊重潜在的绑定执行者,您可以使用dispatch
(or post
):
auto s1_handler = [&](auto chain) {
assert(s1.running_in_this_thread());
dispatch(get_associated_executor(chain, s1), chain);
};
事实上,如果你是sure that chain
将有一个关联的执行程序,您可以接受默认后备(这是系统执行程序):
auto s1_handler = [&](auto chain) {
assert(s1.running_in_this_thread());
dispatch(chain);
};
把它们放在一起
展示简化、扩展测试仪的智慧:
住在科里鲁 http://coliru.stacked-crooked.com/a/5eae9e6029794593
#include <boost/asio.hpp>
#include <functional>
#include <iostream>
namespace asio = boost::asio;
int main() {
asio::thread_pool io(1);
auto s1 = make_strand(io), s2 = make_strand(io);
assert(s1 != s2); // implementation defined! strands may hash equal
auto s1_handler = [&](auto chain) {
assert(s1.running_in_this_thread());
// immediate invocation runs on the current strand:
chain();
// dispatch *might* invoke directly if already on the right strand
dispatch(chain); // 1
dispatch(get_associated_executor(chain, s1), chain); // 2
// posting never immediately invokes, even if already on the right
// strand
post(chain); // 3
post(get_associated_executor(chain, s1), chain); // 4
};
int count_chain_invocations = 0;
auto s2_handler = [&] {
if (s2.running_in_this_thread()) {
count_chain_invocations += 1;
} else {
std::cout << "(note: direct C++ call ends up on wrong strand)\n";
}
};
{
using erased_handler = std::function<void()>;
erased_handler handle = bind_executor(s2, s2_handler);
assert(asio::get_associated_executor(handle, s1) == s1);
}
{
auto handle = bind_executor(s2, s2_handler);
assert(asio::get_associated_executor(handle, s1) == s2);
}
auto handle = bind_executor(s2, s2_handler);
post(s1, std::bind(s1_handler, handle));
io.join();
std::cout << "count_chain_invocations: " << count_chain_invocations << "\n";
}
所有断言都通过,输出符合预期:
(note: direct C++ call ends up on wrong strand)
count_chain_invocations: 4
奖励:如果您需要类型擦除的绑定可调用项怎么办?
无论你做什么,都不要使用std::function
。不过,你可以包一个;
template <typename Sig> struct ErasedHandler {
using executor_type = asio::any_io_executor;
std::function<Sig> _erased;
executor_type _ex;
executor_type get_executor() const { return _ex; }
template <typename F>
explicit ErasedHandler(F&& f)
: _erased(std::forward<F>(f))
, _ex(asio::get_associated_executor(f)) {}
ErasedHandler() = default;
template <typename... Args>
decltype(auto) operator()(Args&&... args) const {
return _erased(std::forward<Args>(args)...);
}
template <typename... Args>
decltype(auto) operator()(Args&&... args) {
return _erased(std::forward<Args>(args)...);
}
explicit operator bool() const { return _erased; }
};
See it 住在科里鲁 http://coliru.stacked-crooked.com/a/3fd0066898eb36d6
在此之前,请注意
- using
any_io_executor
类型也会擦除执行器,这可能会损害性能
- it does not提供良好的后备,只需使用系统执行器来处理未绑定的可调用对象。您可以通过检测它并需要显式构造函数参数等来解决这个问题,但是......
- 所有这些仍然完全忽略其他处理程序属性,例如关联分配器
我可能会避免一般存储类型擦除的可链接处理程序。您通常可以存储由模板类型参数推导的处理程序的实际类型。
PS:事后思考
您可能期待的是这种行为:
template <typename... Args>
decltype(auto) operator()(Args&&... args) const {
// CAUTION: NOT WHAT YOU WANT
boost::asio::dispatch(_ex,
std::bind(_erased, std::forward<Args>(args)...));
}
template <typename... Args>
decltype(auto) operator()(Args&&... args) {
// CAUTION: NOT WHAT YOU WANT
boost::asio::dispatch(_ex,
std::bind(_erased, std::forward<Args>(args)...));
}
看到那个住在科里鲁 http://coliru.stacked-crooked.com/a/3040d582a09f8149
在此方案下,即使是直接的 C++ 调用也会“做正确的事情”。
看起来不错。直到你想到为止。
问题是处理程序不能以这种方式反弹。更具体地说,如果您有一个与“自由线程”执行器关联的处理程序,那么bind_executor(strand, f)
不会有任何影响(除了减慢你的程序),因为f
无论如何,都会令人讨厌地分派给另一个执行者。
所以不要这样做:)