在RX.net和WPF中正确运行长期API调用的方法

时间:2017-10-16 11:46:32

标签: c# wpf system.reactive reactive-programming rx.net

我很高兴在以下方式使用RX在WPF应用程序中进行一些API调用:

    IDisposable disposable = _textFromEventPatternStream
        .ObserveOn(_rxConcurrencyService.Dispatcher)
        .Subscribe(async input =>
            {
                try
                {
                    IsLoading = true;
                    int x = int.Parse(input);
                    var y = await _mathApi.CalcAsync(x);
                    IsLoading = false;
                    Model.Update("", y);
                }
                catch (Exception ex)
                {
                    Model.Update(ex.Message, "Error caught in subscribe, stream continues...");
                }
                finally
                {
                    IsLoading = false;
                }
            },
            ex => Model.Update(ex.Message, "Error, stream will end..."));

但是出于各种原因,我想我可能需要使用SelectMany运算符进行调用并对流进行一些处理。

我希望在api调用中可能会出现一些错误。例如,API端点可能不可用。 API调用之前的一些解析失败。等等我希望Hot Observable继续。我还需要显示一个标准的IsLoading微调器。

现在我也明白了,一旦接收到OnError,序列就不应该继续了。我理解这一点......我只是不喜欢它。

问题是:使用Retry()是否是实现热观察的正确方法,无论错误如何都能继续运行?

以下重写代码有效,但感觉很难吃:

    IDisposable disposable = _textFromEventPatternStream
        .Select(input => int.Parse(input)) // simulating much heavier pre processing, leading to a possible error
        .ObserveOn(_rxConcurrencyService.Dispatcher)
        .Do(_ => IsLoading = true)
        .ObserveOn(_rxConcurrencyService.TaskPool)
        .SelectMany(inputInt => _mathApi.CalcAsync(inputInt))
        .ObserveOn(_rxConcurrencyService.Dispatcher)
        .Do(s => { },
            ex =>
            {
                // this feels like a hack.
                Model.Update(ex.Message, "Error, stream will retry...");
                IsLoading = false;
            })
        .Retry()
        .Subscribe(x => Model.Update("", x),
            ex => Model.Update(ex.Message, "Error, stream will end..."));

我见过一些代码示例,人们使用嵌套流重新订阅故障流。从我所看到的,这似乎是一种常见的方法,但对我而言,它似乎将应该是一个简单的场景转变为难以遵循的情况。

2 个答案:

答案 0 :(得分:2)

如果CalcAsync可能引发错误,我会尝试这样做:

.SelectMany(inputInt => Observable.FromAsync(() => _mathApi.CalcAsync(inputInt)).Retry())

将重试尽可能接近故障观察点。

我还建议进行某种重试计数,以便永久性错误不会挂起可观察数据。

这是一个示例,表明这是有效的。

这失败了:

void Main()
{
    var subject = new Subject<string>();

    IDisposable disposable =
        subject
            .Select(input => int.Parse(input))
            .SelectMany(inputInt => Observable.FromAsync(() => CalcAsync(inputInt)))
            .Subscribe(x => Console.WriteLine(x));

    subject.OnNext("1");
    subject.OnNext("2");
    subject.OnNext("3");
    subject.OnNext("4");
    subject.OnNext("5");
    subject.OnNext("6");
    subject.OnCompleted();
}

private int _counter = 0;

public async Task<int> CalcAsync(int x)
{
    if (_counter++ == 3)
    {
        throw new Exception();
    }
    return await Task.Factory.StartNew(() => -x);
}

通常输出:

-1
-2
-3
Exception of type 'System.Exception' was thrown. 

SelectMany更改为:

.SelectMany(inputInt => Observable.FromAsync(() => CalcAsync(inputInt)).Retry())

现在我明白了:

-1
-3
-2
-4
-5
-6

答案 1 :(得分:0)

跟进,这是我们最终使用的,以防其他人有类似的情况。

我们删除了Retry()运算符,因为它似乎最适合Cold Observables。

所以,考虑到最初的意图,我们希望流继续而不管错误(我们期望网络连接probs等),我们在我们记录错误之后递归调用在异常处理程序中执行订阅的方法,所以日志拾取异常。

它似乎运行良好,并已通过我们的各种方案,包括数据库服务器关闭,api超时异常等...

如果方法中有任何明显的缺陷,请随意评论......

 public void ObserveSelectedTemplateStream(IObservable<dto> textFromEventPatternStream)
            {

                _compositeDisposable.Dispose(); // clean whatever subscriptions we have
                _compositeDisposable = new CompositeDisposable();

                var disposable = textFromEventPatternStream
                .Select(x => Parse(x)) // simulating much heavier pre processing, leading to a possible error
                .ObserveOn(_rxConcurrencyService.Dispatcher)
                .Do(_ => IsLoading = true)
                .ObserveOn(_rxConcurrencyService.TaskPool)
                .SelectMany(x=> _mathApi.CalcAsync(x))
                .ObserveOn(_rxConcurrencyService.Dispatcher)                    .Subscribe(Model.Update,
                    ex => {
                        HandleException(ex);
                        ObserveSelectedTemplateStream(textFromEventPatternStream); // Recursively resubscribe to our stream. We expect errors. It's an API.
                    });

            _compositeDisposable.Add(disposable);


        }