类 客户{
公:
客户(){
线=线程([本]{
io环境_.跑();
});
}
简单异步::协程::懒<极>异步连接(动 主机,动 端口){
极 中=协待 工具::异步连接(主机,端口);//.1
协中 中;//.2
}
~客户(){
io环境_.停止();
如(线.可合并()){
线.合并();
}
}
私:
异网::io环境 io环境_;
线程 线;
};
整 主(){
客户 c;
简单异步::协程::同步等待(c.异步连接());
输出<<"退出\n";//.3
}
该示例很简单,客户
在连接后就析构了,没什么问题.但是运行之后就会有合并
线程的错误,错误即在线程里合并
自己了.为什么?协待
异步连接的协程,当连接成功
后协程返回,此时切换了线程
.
异步连接
返回时是在io环境
的线程里,代码中的.1
在主线程中,.2
在io环境
线程,之后就协中
返回到主
函数的.3
,此时.3
仍然在io环境
线程里,接着客户
就会析构了,此时仍然在io环境
线程里,析构时会调用线.合并();
,然后就有了在io环境
的线程里合并
自己的错误.
这是使用协程
时容易犯错的地方,解决方法就是避免协待
回来之后去析构客户
,或协待
仍然返回到主线程
.这里可考虑用协程条件变量
,在异步连接
时发起新的协程
并传入协程条件变量
,并在连接返回
后置值
,主线程协待
该条件变量,这样连接返回
后就回到主线程了,就可解决在io
线程里合并
自己的问题了.
增加超时处理.
类 客户{
公:
客户():套接字_(io环境_){
线=线程([本]{
io环境_.跑();
});
}
简单异步::协程::懒<极>异步连接(动 主机,动 端口,动 时长){
协程计时器 计时器(io环境_);
超时(计时器,时长).开始([](动&&){});
//.1,启动新协程,来超时处理
极 中=协待 工具::异步连接(主机,端口,套接字_);//假设这里`协待`返回后回到`主线程`
协中 中;
}
~客户(){
io环境_.停止();
如(线.可合并()){
线.合并();
}
}
私:
简单异步::协程::懒<空>超时(动&计时器,动 时长){
极 是超时=协待 计时器.异步等待(时长);
如(是超时){
异网::错误码 忽略误码;
套接字_.关闭(传控::套接字::都关闭,忽略误码);
套接字_.关闭(忽略误码);
}
协中;
}
异网::io环境 io环境_;
传控::套接字 套接字_;
线程 线;
极 是超时_;
};
整 主(){
客户 c;
简单异步::协程::同步等待(c.异步连接("本地主机","9000",5s));
输出<<"退出\n";#3
}
该代码增加了连接超时处理
的协程,注意.1
那里为何新启动协程
,而不能用协待
呢?因为协待
是阻塞语义,协待
会永远超时,启动新协程
不会阻塞
当前协程,从而可去调用异步连接
.
当超时
超时时就关闭套接字
,此时异步连接
就会返回错误
然后返回
到调用者,这似乎可超时处理异步连接
了,但是该代码
有问题.假如异步连接
没有超时会怎样?没有超时的话就返回到主
函数了,然后客户
就析构了,当超时
协程恢复
回来时客户
其实已析构了,此时再去调用成员变量套接字关闭
访问,会有已析构对象
错误.
也许有人会说,那就再协中
之前去取消计时器
不就好了吗?该办法也不行,因为取消计时器
,超时
协程并不会立即返回,仍然会存在访问
已析构对象的问题.
正确做法应该是同步
两个协程,超时
协程和异步连接
协程需要同步,在异步连接
协程返回之前需要确保已完成超时
协程,这样就可避免
访问已析构对象
.
该问题其实也是异步回调安全返回
的经典问题,协程
也同样会遇见该问题,上面提到的同步
两个协程是解决方法之一,另外一个方法就是就像异步安全回调
那样,使用shared_from_this
.
简单异步::协程::懒<极>异步连接(常 串&主机,常 串&端口){
协中 协待 工具::异步连接(主机,端口);
}
简单异步::协程::懒<空>测试连接(){
极 好=协待 异步连接("本地主机","8000");
如(!好){
输出<<"失败,";
}
输出<<"成功";
}
整 主(){
简单异步::协程::同步等待(测试连接());
}
//改后.
简单异步::协程::懒<空>测试连接(){
动 懒=异步连接("本地主机","8000");
极 好=协待 懒;
如(!好){
输出<<"失败";
}
输出<<"成功";
}
代码简单明了,就是测试异步连接
是否成功,运行也是正常的.如果如上稍微改一下
很遗憾,该代码
会使连接总是失败,似乎很奇怪,后面发现原因
是,因为异步连接
的两个参数失效
了,但是写法
和刚开始的写法
几乎一样,为啥后面
该写法会使参数
失效呢?
原因是协待
协程函数时,其实做了两件事:
1,调用协程函数
创建协程,该步骤会创建协程帧
,把参数和局部变量
拷贝到协程帧
里;
2,协待
执行协程函数
;
再看auto lazy=异步连接("localhost","8000");
,该代码调用协程函数
创建了协程
,此时拷贝
到协程帧
里面的是两个临时变量
,该行结束
时临时变量就析构了,下一行协待
执行该协程
时就会出现参数失效
问题.
协待 异步连接("localhost","8000");
这样为什么没问题呢,因为协程
创建和协程调用
都在一行内
完成的,临时变量
知道协程执行
之后才会失效,因此不会有问题.
问题本质其实是C++
临时变量生命期
问题.使用协程
时稍微注意一下就好了,可把const std::string&
改成std::string
,这样就不会有临时变量
生命期问题了,如果不想改参数类型就协待
协程函数就好了,不要分成两行
去执行协程.