你
说
你掩护你的
async_write
一根绳子。但是
你不做这种事
. 你所能看到的就是
在该链中包装完成处理程序
. 但是你在发布异步操作
直接地
.
更糟糕的是,您是从主线程执行的,而与您的
WSClient
实例,这意味着您同时访问的对象实例不是线程安全的。
这是一场数据竞赛
Undefined Behaviour
.
一个天真的解决方案可能是:
std::future<void> write_data_async_future(const std::string &data) {
// shared_ptr is used to ensure data's survival
std::shared_ptr<std::string> data_ptr = std::make_shared<std::string>(data);
std::shared_ptr<std::promise<void> > write_promise = std::make_shared<std::promise<void> >();
post(strand_, [=,self=shared_from_this()] {
websock.async_write(
boost::asio::buffer(*data_ptr),
boost::asio::bind_executor(strand_, std::bind(&WSClientSession::on_write_future, self,
std::placeholders::_1, std::placeholders::_2, data_ptr,
write_promise)));
});
return write_promise->get_future();
}
但那是
不够
. 现在您可以确定您的异步操作或其完成不会同时运行,但是您仍然可以在调用第一个异步操作的完成处理程序之前发布下一个异步操作。
为了解决这个问题,你只需要排队。
老实说,我不知道你为什么这么关注使用未来的同步。这让我们很难做到这一点。如果你能描述一下你/功能上/想要达到的目标,我可以提出一个可能要短得多的解决方案。
代码评审说明
在我意识到代码的意义之前,我花了很多时间阅读你的代码。我不想抢了你一路上的笔记。
警告:这是一个相当长的代码潜水。我之所以提供它,是因为一些见解可能会帮助您了解如何重新构造代码。
我开始阅读异步代码链直到
on_handshake
(哪个)
集合
这个
started_promise
价值)。
然后我去了马尔斯特罗姆那是你的
main
功能。你的主要功能是50行代码?!有几个并行容器和重复的手动嵌套循环?
这是我经过重构后得到的:
int main() {
std::vector<actor> actors(1);
for (auto& a : actors) {
a.client = std::make_shared<WSClient>();
a.session_start_future = a.client->start("127.0.0.1", "8085");
a.messages.resize(50);
}
for (auto& a : actors) { a.session_start_future.get(); }
for (auto& a : actors) { for (auto& m : a.messages) {
m.write_future = a.client->write_data_async_future("Hello");
} }
for (auto& a : actors) { for (auto& m : a.messages) {
m.read_future = a.client->read_data_async_future();
} }
for (auto& a : actors) { for (auto& m : a.messages) {
m.write_future.get();
std::string result = m.read_future.get();
} }
}
所有的数据结构都被折叠到小助手中
actor
:
struct actor {
std::shared_ptr<WSClient> client;
std::future<void> session_start_future;
struct message {
std::string message = GenerateRandomString(20);
std::future<void> write_future;
std::future<std::string> read_future;
};
std::vector<message> messages;
};
我们现在大约一个小时的代码审查,没有任何收获,除了我们现在可以告诉
主要的
正在做,并且有信心循环变量或其他东西不会有任何小错误。
拾起
开始写作时:
write_data_async_future
. 等待。还有
write_data_async
和
write_data_sync
. 为什么?你会想看的
更糟的是,
威斯林特
只是把这些转达给
单一的
会议。为什么有区别
威斯林特
和
WSClientSession
在这一点上?我说,没有。
再蒸发30行不太有用的代码,我们仍然有同样的失败,所以这是好的。
我们去哪了。
写入数据异步未来
. 哦,是的,我们需要非未来版本吗?不,所以,还有40行代码没了。
现在,说真的:
写入数据异步未来
:
std::future<void> write_data_async_future(const std::string &data) {
// shared_ptr is used to ensure data's survival
std::shared_ptr<std::string> data_ptr = std::make_shared<std::string>(data);
std::shared_ptr<std::promise<void> > write_promise = std::make_shared<std::promise<void> >();
websock.async_write(
boost::asio::buffer(*data_ptr),
boost::asio::bind_executor(strand_, std::bind(&WSClientSession::on_write_future, shared_from_this(),
std::placeholders::_1, std::placeholders::_2, data_ptr,
write_promise)));
return write_promise->get_future();
}
看。。。奥凯什等等,有
on_write_future
?这可能意味着我们需要蒸发更多未使用的代码行。看。。。是的。噗噗,走了。
现在,diffstat看起来像这样:
test.cpp | 683 +++++++++++++++++++++++----------------------------------------
1 file changed, 249 insertions(+), 434 deletions(-)
回到那个函数,让我们看看
论未来
:
void on_write_future(boost::system::error_code ec, std::size_t bytes_transferred,
std::shared_ptr<std::string> data_posted,
std::shared_ptr<std::promise<void> > write_promise) {
boost::ignore_unused(bytes_transferred);
boost::ignore_unused(data_posted);
if (ec) {
try {
throw std::runtime_error("Error thrown while performing async write: " + ec.message());
} catch (...) {
write_promise->set_exception(std::current_exception());
}
return;
}
write_promise->set_value();
}
一些问题。过去的一切都被忽略了。我知道您传递共享ptr的目的,但也许您应该将它们作为操作对象的一部分传递,以避免有这么多单独的共享ptr。
抛出异常只是为了抓住它?嗯。我不确定。或许只是设定了一个新的例外:
if (ec) {
write_promise->set_exception(
std::make_exception_ptr(std::system_error(ec, "async write failed")));
} else {
write_promise->set_value();
}
即便如此,现在还是有一个概念上的问题。你自由使用的方式
get()
不插手
主要的
这意味着任何连接中的任何错误都将中止所有操作。如果错误只是中止一个连接/会话/客户机,这将非常有用。在你的代码中,它们都是同义的
io_context
和
thread
)
旁注:您将线程存储为一个成员,但总是分离它。这意味着从那时起这个成员就没用了。
在这一点上,我暂停了复习,碰巧我的脑波告诉我这个问题。我锻炼的半生不熟的结果是
here
. 请注意,您不能使用它,因为它实际上并不能解决问题。但它可能在其他方面有所帮助?