如果CancellationDisposable与MultiCast一起使用,则不会触发OnCompleted

时间:2014-09-04 08:54:35

标签: c# system.reactive

我使用Observable.Create使用CancellationDisposable实现了一个observable。 由于我想在创建后分享observable,我使用Publish和更晚Connect。 如果Connect返回的一次性物品被丢弃,则CancellationDisposable也会被处理,并且会抛出OperationCancelledException。 问题是观察者o1永远不会被告知异常。 为什么会发生这种情况?如何熟练使用CancellationDisposablePublish组合代码?

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

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>之前使用FinallyPublish运算符。

请注意,我稍微更改了代码以使用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