使用 ToEventPattern 和 Timeout 时 RX.Net 中的异常处理

Exception handling in RX.Net when using ToEventPattern and Timeout

我正在使用 C# 中的 RX 编写一些代码,这些代码必须通过发出事件与旧系统交互。

总而言之,我有一个可观察对象,需要在可观察对象完成时发出一个事件,如果检测到超时异常则需要发出另一个事件。主要问题是如何最好地处理异常。

我对 RX 比较陌生,所以虽然我找到了解决方案,但我不能确定是否有更好或更合适的方法可以更好地使用 RX 扩展。

这不是真正的代码,但表明了我的思维模式:

    public delegate void SuccessHandler(object sender, SuccessEventArgs e);
    public event SuccessHandler OnSuccess;

    public delegate void TimeoutHandler(object sender, TimeoutEventArgs e);
    public event TimeoutHandler OnTimeout;

    var id;
    var o = Observable.Return()     // <- this would be a fetch from an asynchronous source
        .Where(r=>r.status=="OK")
        .Timeout(new Timespan(0,0,30)
        .Do(r=> {
             id=r.Id           // <-- Ugh! I know this shouldn't be done!
         }
        .Subscribe(r => {

             var statusResponse= new StatusResponse()
             {
               Id = r.Id
               Name = r.Name
                  Message = "The operation completed successfully",
                  Status = Status.Success
             };

             if (OnSuccess == null) return;
                OnSuccess (this, new SuccessEventArgs(statusResponse);
           },
           e =>
           {
             _logger.LogError(e, "A matching response was not returned in a timely fashion");

             if (OnTimeout == null) return;
             OnTimeout(this, new TimeoutEventArgs(id));
           });

如果我不需要检测超时并根据超时采取行动就好了;我已经弄清楚如何用 Subscribe 代替 ToEventPattern:

         ...
    .Select(r =>
    {
         var statusResponse= new StatusResponse()
         {
           Id = r.Id
           Name = r.Name
           Message = "The operation completed successfully",
           Status = Status.Success
         };
         return new EventPattern<SuccessEventArgs>(this, new SuccessEventArgs(statusResponse));
    })
    .ToEventPattern();

但是,我希望能够检测到超时(以及可能的其他异常)。我对 Catch 的实验没有成功,因为我似乎无法让类型正确排列,可能是因为我不太明白发生了什么。

非常感谢对此的意见。这是一个可以接受的解决方案吗?我该如何改进它?谁能给我指出一些很好的在线参考资料,这些参考资料将解释如何完成这种流量控制和异常处理(到目前为止我看到的所有示例似乎都没有你想要发出的真实案例事件 and 将其与异常处理相结合)。

提前致谢

您可以很容易地从可观察对象中分支出来,例如

var a = Observable.Range(0, 10);
var b = a.Select(x => x * x);
var c = a.Select(x => x * 10);

一句警告——如果可观察对象是,这将导致生产者函数为每个订阅运行。如果不清楚,请查看 hotcold observables 之间的区别。

我创建了一个解决方案,它从源可观察对象创建了两个分支,并将每个分支转换为一个事件:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");

        var service = new Service();
        var apiCall = service.CallApi();
        apiCall.OnSuccess.OnNext += (_, __) => Console.WriteLine("Success!");
        apiCall.OnTimeout.OnNext += (_, __) => Console.WriteLine("Timeout!");
        Console.ReadLine();
    }
}
class SuccessEventArgs{}
class TimeoutEventArgs{}

class ApiCall
{
    public IEventPatternSource<SuccessEventArgs> OnSuccess {get;}

    public IEventPatternSource<TimeoutEventArgs> OnTimeout {get;}

    public ApiCall(IEventPatternSource<SuccessEventArgs> onSuccess, IEventPatternSource<TimeoutEventArgs> onTimeout)
    {
        OnSuccess = onSuccess;
        OnTimeout = onTimeout;
    }
}
class Service
{
    public ApiCall CallApi()
    {    
        var apiCall = Observable
            .Timer(TimeSpan.FromSeconds(3))
            .Do(_ => Console.WriteLine("Api Called"))
            .Select(_ => new EventPattern<SuccessEventArgs>(null, new SuccessEventArgs()))
            // .Timeout(TimeSpan.FromSeconds(2)) // uncomment to time out
            .Timeout(TimeSpan.FromSeconds(4))
            // the following two lines turn the "cold" observable "hot"
            // comment them out and see how often "Api Called" is logged
            .Publish()
            .RefCount();

        var success = apiCall
            // ignore the TimeoutException and return an empty observable
            .Catch<EventPattern<SuccessEventArgs>, TimeoutException>(_ => Observable.Empty<EventPattern<SuccessEventArgs>>())
            .ToEventPattern();

        var timeout = apiCall
            .Materialize() // turn the exception into a call to OnNext rather than OnError
            .Where(x => x.Exception is TimeoutException)
            .Select(_ => new EventPattern<TimeoutEventArgs>(null, new TimeoutEventArgs()))
            .ToEventPattern();

        return new ApiCall(success, timeout);
    }
}