如何使用.Net IObservable ::使用ReactiveUI中的WhenAnyValue重试

时间:2016-06-15 08:51:24

标签: c# .net exception system.reactive reactiveui

如果我有一个INPC支持类数字,其中包含两个属性 A B 。我可以编写像

这样的代码
Numbers numbers = new Numbers();
IObservable<double> o = numbers.WhenAnyValue(p=>p.A,p=>p.B,(a,b)=>a/b);

WhenAnyValue是ReactiveUI库中的一个实用程序方法,用于从属性更改事件中组合observable。如果我然后写。

o.Subscribe(v=>Console.WriteLine(v));

只要A或B发生变化,它就会打印 a / b 。这一切都很好,直到我设置

numbers.B = 0;

现在 a / b 会抛出DivideByZeroException,observable将终止。然而,这是一个用户界面。我不希望observable终止。我只是想要忽略该异常或记录它并继续前进。第一次尝试是看到IObservable包含一个名为Retry的扩展方法,它将在异常后重新连接到observable。我们试试

Numbers numbers = new Numbers();
IObservable<double> o = numbers
  .WhenAnyValue(p=>p.A,p=>p.B,(a,b)=>a/b)
  .Retry();

o.Subscribe(v=>Console.WriteLine(v));

但是当我执行 numbers.B = 0 时,重试将忽略该异常并重新连接,并会立即一次又一次地失败,因为 WhenAnyValue 总是会发送一个事件订阅。

所以看起来我需要的是一个重试,它会在重新连接后忽略第一个输入 iff 它与导致错误断开第一个输入的输入相同,除了我不认为这可以用RX。

有什么想法吗?

完整测试用例

以下测试用例不会终止。

   public class Numbers : ReactiveObject
    {
        int _A;
        public int A 
        {
            get { return _A; }
            set { this.RaiseAndSetIfChanged(ref _A, value); }
        }

        int _B;
        public int B 
        {
            get { return _B; }
            set { this.RaiseAndSetIfChanged(ref _B, value); }
        }
    }

    [Fact]
    public void TestShouldTerminate()
    {

        var numbers = new Numbers();
        var o = numbers
            .WhenAnyValue(p => p.A, p => p.B, Tuple.Create)
            .Select(v=>v.Item1/v.Item2)
            .Select(v=>v+1)
            .Retry();

        double value = 0;
        o.Subscribe(v => value = v);

        numbers.A = 10;
        numbers.B = 20;

        value.Should().Be(1.5);
    }
 }



    }



    }

1 个答案:

答案 0 :(得分:0)

这不能在vanilla RX中处理。我创建了一个名为

的包装器
IObservableExceptional
IObserverExceptional

更改了RX中错误处理的标准合同。错误现在不再终止observable。它支持LINQ,对于大多数用途应该是相当透明的。通过的测试用例是

    [Fact]
    public void ErrorsCanBePropogated()
    {
        var numbers = new Numbers();

        var list = new List<double>();
        var errors = new List<Exception>();

        numbers
            .WhenAnyValue(p => p.A, p => p.B, Tuple.Create)
            .ToObservableExceptional()
            .Select(v => v.Item1/v.Item2)
            .Subscribe(onNext: val=>list.Add(val), onError:err=>errors.Add(err));


        list.Count.Should().Be(0);
        errors.Count.Should().Be(1);

        numbers.A = 10;

        list.Count.Should().Be(0);
        errors.Count.Should().Be(2);

        numbers.B = 5;

        list.Count.Should().Be(1);
        list[0].Should().Be(2.0);
        errors.Count.Should().Be(2);
    }

三个新界面

public interface IObservableExceptional<T>
{
    void Subscribe(IObserverExceptional<T> observer);

    IObservable<IExceptional<T>> Observable { get; }
}

public interface IObserverExceptional<T>
{
    void OnNext(IExceptional<T> t);
    void OnCompleted();
    IObserver<IExceptional<T>> Observer { get; }
}

public interface IExceptional<out T> : IEnumerable<T>
{
    bool HasException { get; }
    Exception Exception { get; }
    T Value { get; }
    string ToMessage();
    void ThrowIfHasException();
}

IObservableException和IExceptional都支持LINQ(即它们是Monads)

IObservableExceptional的Select或SelectMany组合器中抛出的任何异常都包装为IExceptional对象并传递给订阅者。错误不会终止订阅。

存储库位于

https://github.com/Weingartner/Exceptional