我使用Observable.Create
使用CancellationDisposable
实现了一个observable。
由于我想在创建后分享observable,我使用Publish
和更晚Connect
。
如果Connect
返回的一次性物品被丢弃,则CancellationDisposable
也会被处理,并且会抛出OperationCancelledException
。
问题是观察者o1
永远不会被告知异常。
为什么会发生这种情况?如何熟练使用CancellationDisposable
和Publish
组合代码?
var obs = Observable.Create<int>(
observer =>
{
var cancel = new CancellationDisposable();
var scheduler = Scheduler.Default.Schedule(() =>
{
try
{
observer.OnNext(1);
Thread.Sleep(2000);
cancel.Token.ThrowIfCancellationRequested();
observer.OnNext(2);
observer.OnCompleted();
}
catch (Exception ex)
{
observer.OnError(ex);
}
});
return new CompositeDisposable(cancel, scheduler);
})
.ObserveOn(ThreadPoolScheduler.Instance)
.Publish();
var o1 = obs.Subscribe(x => Console.WriteLine("Next: {0}", x), x => Console.WriteLine("Error: {0}", x), () => Console.WriteLine("Completed"));
var connection = obs.Connect();
Console.WriteLine("Press key to cancel");
Console.ReadLine();
connection.Dispose();
Console.ReadLine();
如果立即按Enter键输出:
Press key to cancel
Next: 1
答案 0 :(得分:2)
处理连接会释放所有订阅(内部)。这是Rx的取消模型。处理订阅时,观察者不再接收任何类型的通知。
见Rx Design Guidelines文件中的§4.4。
如果您想在取消观察者时收到通知,请使用Finally运算符。请注意,它通常适用于终止,因此您的操作也会在完成或失败时调用。
var o1 = obs.Finally(() => Console.WriteLine("Terminated")).Subscribe(...);
<强>更新强>
阅读我的回答,我意识到我并不完全清楚。只有在您自己处理订阅时才会调用Finally
运算符。处置连接只会将内部订阅处理为已发布的observable,从而使您的订阅处于“活动状态”。这样做的原因是您始终可以重新连接已发布的observable,您的订阅将继续接收通知。因此,o1
实际上并没有被取消。
此外,无论如何都无法调用 OnCompleted
,因为只需重新连接observable就可以在同一个订阅上多次调用它,这当然会破坏Rx语法。请参阅Rx设计指南中的§4.1。
更新2:
正如评论中所提到的,虽然取消时不调用OnCompleted
,但是当observable成功终止时会调用它;但是,观察者将来不再收到任何通知(以满足Rx语法),即使在后续重新连接时也是如此。此外,所有Finally
运算符都将执行,因为序列已终止。
更新3:回答
当取消一个可连接的观察者时,OnCompleted
当然可以召唤每个观察者,虽然我不推荐这个,因为它非常奇怪,因为它违背了Rx中的颗粒,但是我必须提供它从技术上回答了原始问题。此解决方案的关键是在调用Subject<T>
之前使用Finally
和Publish
运算符。
请注意,我稍微更改了代码以使用Async Iterator并删除了错误处理错误(请参阅下面的注释)。
using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
namespace RxLabs.Net45
{
class PublishFinallyLab
{
public static void Main()
{
var obsOrCancellation = new Subject<int>();
var obs = Observable.Create<int>(
async (observer, cancel) =>
{
observer.OnNext(1);
await Task.Delay(TimeSpan.FromSeconds(2), cancel).ConfigureAwait(false);
if (!cancel.IsCancellationRequested)
{
observer.OnNext(2);
}
})
.Finally(obsOrCancellation.OnCompleted)
.Publish();
obs.Subscribe(obsOrCancellation);
var o1 = obsOrCancellation
.Finally(() => Console.WriteLine("Finally!"))
.Subscribe(
x => Console.WriteLine("Next: {0}", x),
ex => Console.WriteLine("Error: {0}", ex),
() => Console.WriteLine("Completed"));
do
{
using (var connection = obs.Connect())
{
Console.WriteLine("Press any key to cancel.");
Console.ReadKey();
}
Console.WriteLine("Press any key to continue.");
Console.ReadKey();
}
while (true);
}
}
}
-
在一个不相关的但重要的注释中,它是一种反模式来捕捉观察者抛出的异常。不要这样做。您应该完全删除try..catch
。您的observable中没有代码调用可能抛出的非观察者代码,因此您根本不应该调用OnError
。