简体   繁体   English

Rx.net 在可观察到的断开/错误上实现重试功能

[英]Rx.net implement retry functionality on disconnect/error in observable

Below is the following code:以下是以下代码:

    public class FooService
    {
        private ITransportService _transportService;
        public FooService(ITransportService transportService)
        {
            _transportService = transportService;
            _transportService.Connect();
        }

        public IDisposable Subscribe(IObserver<FooData> observer)
        {
            return _transportService.GetObservable()
                .Subscribe(observer);
        }
    }

    public interface ITransportService
    {
        ConnectionState State { get; }
        bool Connect();
        IObservable<FooData> GetObservable();
    }

    public class ClientConsumingProgram
    {
        class FooObserver : IObserver<FooData>
        {
            public void OnNext(FooData value)
            {
                //Client consuming without interruption
            }
            //.. on error.. onCompleted
        }
        public static void Main()
        {
            var fooService = new FooService(transportService);
            var fooObserver = new FooObserver();
            var disposable = fooService.Subscribe(fooObserver);
        }
    }

I want to implement following:我想实现以下内容:

When transport service is disconnected (socket closed from server), I want application to retry for few times, but foo service first needs to call Connect on _transportService and then once State is connected, call GetObservable.当传输服务断开(从服务器关闭套接字)时,我希望应用程序重试几次,但 foo 服务首先需要在_transportService上调用Connect ,然后一旦State连接,调用 GetObservable。

Desired result is OnNext on FooObserver keeps on ticking on client side, if _transportService is connect again before max retry, and once it's exceeds max error OnError should be fired.期望的结果是FooObserver上的OnNext继续在客户端滴答作响,如果_transportService在最大重试之前再次连接,并且一旦超过最大错误 OnError 应该被触发。

Can someone point me to the right direction for implementing this?有人可以指出我实施这一点的正确方向吗?


UPDATE更新

public class FooService
{
    private ITransportService _transportService;
    public FooService(ITransportService transportService)
    {
        _transportService = transportService;
        _transportService.Connect();
    }

    public IDisposable Subscribe(IObserver<FooData> observer)
    {
        return _transportService.GetConnectionStateObservable()
        .Select(cs => cs == ConnectionState.Open)
        .DistinctUntilChanged()
        .Select(isOpen => isOpen
            ? _transportService.GetObservable()   //if open, return observable
            : Observable.Start(() => _transportService.Connect()) //if not open, call connect and wait for connection to open
                .IgnoreElements()
                .Select(_ => default(FooData))
                .Concat(Observable.Never<FooData>())
        )
        .Switch()
        .Subscribe(observer);
    }
}

public interface ITransportService
{
    IObservable<ConnectionState> GetConnectionStateObservable();
    bool Connect();
    IObservable<FooData> GetObservable();
}

public class FooData
{
    public int Id { get; set; }
    public string Msg { get; set; }
}

public enum ConnectionState
{
    Open,
    Close
}

public class FooMockTransportService : ITransportService
{
    public ConnectionState State { get; set; }
    private BehaviorSubject<ConnectionState> _connectionSubject = new BehaviorSubject<ConnectionState>(ConnectionState.Close);
    private bool _shouldDisconnect;

    public FooMockTransportService()
    {
        _shouldDisconnect = true;
    }

    public bool Connect()
    {
        State = ConnectionState.Open;
        _connectionSubject.OnNext(ConnectionState.Open);
        return true;
    }

    public IObservable<ConnectionState> GetConnectionStateObservable()
    {
        return _connectionSubject.AsObservable();
    }

    public IObservable<FooData> GetObservable()
    {
        return Observable.Create<FooData>(
            o=>
            {
                TaskPoolScheduler.Default.Schedule(() =>
                {
                    o.OnNext(new FooData { Id = 1, Msg = "First" });
                    o.OnNext(new FooData { Id = 2, Msg = "Sec" });

                    //Simulate disconnection, ony once
                    if(_shouldDisconnect)
                    {
                        _shouldDisconnect = false;
                        State = ConnectionState.Close;
                        o.OnError(new Exception("Disconnected"));
                        _connectionSubject.OnNext(ConnectionState.Close);
                    }

                    o.OnNext(new FooData { Id = 3, Msg = "Third" });
                    o.OnNext(new FooData { Id = 4, Msg = "Fourth" });
                });
                return () => { };
            });
    }
}

public class Program
{
    class FooObserver : IObserver<FooData>
    {
        public void OnCompleted()
        {
            throw new NotImplementedException();
        }

        public void OnError(Exception error)
        {
            Console.WriteLine(error);
        }

        public void OnNext(FooData value)
        {
            Console.WriteLine(value.Id);
        }
    }
    public static void Main()
    {
        var transportService = new FooMockTransportService();
        var fooService = new FooService(transportService);
        var fooObserver = new FooObserver();
        var disposable = fooService.Subscribe(fooObserver);
        Console.Read();
    }
}

Code is compliable and also contains suggestions for Shlomo.代码是合规的,还包含对 Shlomo 的建议。 Current output:当前 output:

1
2
System.Exception: Disconnected

Desired output, on disconnect it should catch and retry every 1 sec as an example to see whether it's connected or not:所需的 output,在断开连接时,它应该每 1 秒捕获并重试一次,以查看它是否已连接:

1
2
1
2
3
4

If you control ITransportService , I would recommend adding a property:如果您控制ITransportService ,我建议添加一个属性:

public interface ITransportService
{
    ConnectionState State { get; }
    bool Connect();
    IObservable<FooData> GetObservable();
    IObservable<ConnectionState> GetConnectionStateObservable();
}

Once you can get the states in an observable fashion, producing the observable becomes easier:一旦你能以可观察的方式获得状态,产生可观察的就变得更容易了:

public class FooService
{
    private ITransportService _transportService;
    public FooService(ITransportService transportService)
    {
        _transportService = transportService;
        _transportService.Connect();
    }

    public IDisposable Subscribe(IObserver<FooData> observer)
    {
        return _transportService.GetConnectionStateObservable()
            .Select(cs => cs == ConnectionState.Open)
            .DistinctUntilChanged()
            .Select(isOpen => isOpen 
                ? _transportService.GetObservable()   //if open, return observable
                : Observable.Start(() => _transportService.Connect()) //if not open, call connect and wait for connection to open
                    .IgnoreElements()
                    .Select(_ => default(FooData))
                    .Concat(Observable.Never<FooData>())
            )
            .Switch()
            .Subscribe(observer);
    }
}

If you don't control ITransportService , I would recommend creating an interface that inherits from it where you can add a similar property.如果您不控制ITransportService ,我建议您创建一个从它继承的接口,您可以在其中添加类似的属性。

As an aside, I would recommend you ditch FooObserver , you almost never need to fashion your own observer.顺便说一句,我建议你FooObserver ,你几乎不需要塑造自己的观察者。 Expose the observable, and calling a Subscribe overload on the Observable normally does the trick.公开可观察对象,并在可观察对象上调用Subscribe重载通常可以解决问题。

I can't test any of this though: there's no clarity as to what the retry logic should be like, what the return value for Connect means, or what the ConnectionState class is, and the code doesn't compile.但是我无法测试其中的任何一个:重试逻辑应该是什么样的, Connect的返回值是什么意思,或者ConnectionState class 是什么,并且代码无法编译,都不清楚。 You should try to fashion your question as a mcve .您应该尝试将您的问题塑造为mcve


UPDATE :更新

The following handles the test code as expected:以下按预期处理测试代码:

public IDisposable Subscribe(IObserver<FooData> observer)
{
    return _transportService.GetConnectionStateObservable()
        .Select(cs => cs == ConnectionState.Open)
        .DistinctUntilChanged()
        .Select(isOpen => isOpen
            ? _transportService.GetObservable()   //if open, return observable
                .Catch(Observable.Never<FooData>())
            : Observable.Start(() => _transportService.Connect()) //if not open, call connect and wait for connection to open
                .IgnoreElements()
                .Select(_ => default(FooData))
                .Concat(Observable.Never<FooData>())
        )
        .Switch()
        .Subscribe(observer);
}

Only change from the original posted code is the additional .Catch(Observable.Never<FooData>()) .与原始发布代码的唯一变化是附加的.Catch(Observable.Never<FooData>()) As written, this code will run forever.如所写,此代码将永远运行。 I hope you have some way to terminate the observable external to what's posted.我希望您有某种方法可以终止发布内容的外部可观察对象。

You cannot write Rx code that effectively executes like this:你不能编写像这样有效执行的 Rx 代码:

o.OnNext(new FooData { Id = 1, Msg = "First" });
o.OnNext(new FooData { Id = 2, Msg = "Sec" });

o.OnError(new Exception("Disconnected"));

o.OnNext(new FooData { Id = 3, Msg = "Third" });
o.OnNext(new FooData { Id = 4, Msg = "Fourth" });

The contract for an observable is a stream of zero or more values that ends with either an error or a complete signal. observable 的合约是一个零个或多个值的 stream,以错误或完整信号结束。 They cannot emit further values.他们不能发出更多的值。

Now, I appreciate that this code might be for testing purposes, but if you create an impossible stream you'll end up writing impossible code.现在,我很欣赏这段代码可能用于测试目的,但如果你创建了一个不可能的 stream 你最终会写出不可能的代码。

The correct approach is to use .Switch() , as per Shlomo's answer.根据 Shlomo 的回答,正确的方法是使用.Switch() If there's some delay in connection then the GetConnectionStateObservable should only return a value when it is connected.如果连接有一些延迟,那么GetConnectionStateObservable应该只在连接时返回一个值。 Shlomo's answer remains correct. Shlomo 的回答仍然正确。

Elaborating my comment:阐述我的评论:

As Shlomo already shown in his answer how you can take advantage of connection state observable, what I guess you want is to subscribe it again when disconnect happen.正如 Shlomo 已经在他的回答中展示了如何利用连接 state 可观察的,我想你想要的是在断开连接时再次订阅它。

For that use Observable.Defer为此使用 Observable.Defer

return Observable.Defer(() => your final observable)

and now on disconnect if you want to subscribe again use Retry现在断开连接,如果您想再次订阅,请使用重试

return Observable.Defer(() => your final observable).Retry(3)

but you might need to delay your retries, either linearly or with exponential back-off strategy, for this use DelaySubscription但是您可能需要延迟重试,无论是线性还是使用指数退避策略,为此使用DelaySubscription

return Observable.Defer(() => your_final_observable.DelaySubscription(strategy)).Retry(3)

here's the final code, with retry every second:这是最终代码,每秒重试一次:

        public IDisposable Subscribe(IObserver<FooData> observer)
        {
            return Observable.Defer(() => {
                return _transportService.GetConnectionStateObservable()
            .Select(cs => cs == ConnectionState.Open)
            .DistinctUntilChanged()
            .Select(isOpen => isOpen
                ? _transportService.GetObservable()   //if open, return observable
                : Observable.Start(() => _transportService.Connect()) //if not open, call connect and wait for connection to open
                    .IgnoreElements()
                    .Select(_ => default(FooData))
                    .Concat(Observable.Never<FooData>())
            )
            .Switch().DelaySubscription(TimeSpan.FromSeconds(1));
            })
            .Retry(2)
            .Subscribe(observer);
        }

Some caveats to remember :需要记住的一些注意事项

This DelaySubscription will delay first call also, so if it's a problem, create a count variable and only when count > 0 use DelaySubscription else use normal observable.这个 DelaySubscription 也会延迟第一次调用,所以如果有问题,创建一个 count 变量,只有当 count > 0 时才使用 DelaySubscription ,否则使用正常的 observable。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM