为什么拼接多个FirstAsync会阻塞父Observable完成且仅最后一个onCompleted触发?
链式调用FirstAsync的回调触发逻辑解答
核心原因
Rx操作符的订阅和事件传播遵循从下游到上游逐层订阅、从上游到下游逐层传递事件的规则,而FirstAsync的内置逻辑是:接收到上游的第一个OnNext事件后,会立刻向下游传递该事件,同时主动取消对上游的订阅,并直接向下游发送OnCompleted事件,不会等待上游自身执行完成逻辑。
你的示例执行顺序
我们可以把你给出的链式结构拆成逐层依赖关系:原始Observable → 第一个FirstAsync → 第一个Do → 第二个FirstAsync → 第二个Do
实际执行的时间线如下:
- 订阅触发后,原始Observable发射第一个元素
- 第一个FirstAsync收到元素,向第一个Do传递
OnNext事件 - 第一个Do执行
OnNext回调,把元素继续传给第二个FirstAsync - 第二个FirstAsync收到第一个元素后,同时触发两个动作:
- 向第二个Do传递
OnNext事件 - 主动取消对上游(第一个Do)的订阅,同时向第二个Do传递
OnCompleted事件
- 向第二个Do传递
- 此时第一个FirstAsync本来要向第一个Do传递
OnCompleted事件,但因为下游订阅已经被取消,按照Rx协议,上游不会再向已取消订阅的下游发送任何事件,因此第一个Do的OnCompleted回调永远不会被触发。
关于“阻塞父Observable完成”的误解
实际上父Observable并没有被阻塞,只是它的OnCompleted事件的传播路径被下游的FirstAsync主动截断了。如果你的原始Observable本身有完成逻辑,它依然会正常执行自己的完成流程,只是这个完成事件不会传递到被截断的上游回调里。
需触发所有OnCompleted的解决方案
如果需要每个FirstAsync的后续回调都正常触发,可以用多播操作符把原始Observable的事件共享给多个独立的FirstAsync订阅,不要写成链式调用:
// 多播原始Observable的事件 var sharedSource = Observable.XXX().Publish(); // 第一个独立订阅 sharedSource .FirstAsync() .Do(_ => { }, onCompleted: () => { // 会正常触发 }) .Subscribe(); // 第二个独立订阅 sharedSource .FirstAsync() .Do(_ => { }, onCompleted: () => { // 会正常触发 }) .Subscribe(); // 启动多播 sharedSource.Connect();
内容的提问来源于stack exchange,提问作者Vincent P
相关产品推荐
相关产品推荐

