C# 使用Rx为webservice调用创建轮询请求

C# 使用Rx为webservice调用创建轮询请求,c#,recursion,system.reactive,reactive-programming,polling,C#,Recursion,System.reactive,Reactive Programming,Polling,在C中使用Rx,我试图创建一个对RESTAPI的轮询请求。我面临的问题是,可观察到的需要按顺序发送响应。表示如果请求A在X时间发出,请求B在X+dx时间发出,且B的响应在A之前,则可观察表达式应忽略或取消请求A 我已经编写了一个示例代码,试图描述这个场景。如何修复它以仅获取最新的响应并取消或忽略以前的响应 class Program { static int i = 0; static void Main(string[] args)

在C中使用Rx,我试图创建一个对RESTAPI的轮询请求。我面临的问题是,可观察到的需要按顺序发送响应。表示如果请求A在X时间发出,请求B在X+dx时间发出,且B的响应在A之前,则可观察表达式应忽略或取消请求A

我已经编写了一个示例代码,试图描述这个场景。如何修复它以仅获取最新的响应并取消或忽略以前的响应

 class Program
    {
        static int i = 0;

        static void Main(string[] args)
        {
            GenerateObservableSequence();

            Console.ReadLine();
        }

        private static void GenerateObservableSequence()
        {
            var timerData = Observable.Timer(TimeSpan.Zero,
                TimeSpan.FromSeconds(1));

            var asyncCall = Observable.FromAsync<int>(() =>
            {
                TaskCompletionSource<int> t = new TaskCompletionSource<int>();
                i++;

                int k = i;
                var rndNo = new Random().Next(3, 10);
                Task.Delay(TimeSpan.FromSeconds(rndNo)).ContinueWith(r => { t.SetResult(k); });
                return t.Task;
            });

            var obs = from t in timerData
            from data in asyncCall
            select data;

            var hot = obs.Publish();
            hot.Connect();

                hot.Subscribe(j => 
            {
                Console.WriteLine("{0}", j);
            });
        }
    }
@Enigmativity answer之后:添加了轮询Aync功能,以始终获取最新响应:

 public static IObservable<T> PollingAync<T> (Func<Task<T>> AsyncCall, double TimerDuration)
        {
            return Observable
         .Create<T>(o =>
         {
             var z = 0L;
             return
                 Observable
                     .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(TimerDuration))
                     .SelectMany(nr =>
                         Observable.FromAsync<T>(AsyncCall),
                         (nr, obj) => new { nr, obj})
                     .Do(res => z = Math.Max(z, res.nr))
                     .Where(res => res.nr >= z)
                     .Select(res => res.obj)
                     .Subscribe(o);
         });

    }

这是一种常见的情况,可以简单地解决

您的示例代码的关键部分是

var obs = from t in timerData
          from data in asyncCall
          select data;
这可以理解为timerData中的每个值在asyncCall中获取所有值。这是SelectMany或FlatMap运算符。SelectMany运算符将从内部序列asyncCall中获取所有值,并在接收时返回它们的值。这意味着您可以得到无序的值

当外部序列timerData生成新值时,需要取消以前的内部序列。要做到这一点,我们需要使用开关操作符

var obs = timerData.Select(_=>asyncCall)
                   .Switch();
完整代码可以清理到以下位置。已删除冗余发布/连接,按键时释放订阅

班级计划 { 静态int i=0

    static void Main(string[] args)
    {
        using (GenerateObservableSequence().Subscribe(x => Console.WriteLine(x)))
        {
            Console.ReadLine();
        }
    }

    private static IObservable<int> GenerateObservableSequence()
    {
        var timerData = Observable.Timer(TimeSpan.Zero,
            TimeSpan.FromSeconds(1));

        var asyncCall = Observable.FromAsync<int>(() =>
        {
            TaskCompletionSource<int> t = new TaskCompletionSource<int>();
            i++;

            int k = i;
            var rndNo = new Random().Next(3, 10);
            Task.Delay(TimeSpan.FromSeconds(rndNo)).ContinueWith(r => { t.SetResult(k); });
            return t.Task;
        });

        return from t in timerData
               from data in asyncCall
               select data;
    }
}

让我们从简化代码开始

这基本上是相同的代码:

var rnd = new Random();

var i = 0;

var obs =
    from n in Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
    let r = ++i
    from t in Observable.Timer(TimeSpan.FromSeconds(rnd.Next(3, 10)))
    select r;

obs.Subscribe(Console.WriteLine);
我得到这样的结果:

2 1 3 4 8 5 11 6 9 7 10 那么,现在请满足您的要求:

如果请求A在X时间发出,请求B在X+dx时间发出,且B的响应在A之前,则可观察表达式应忽略或取消请求A

代码如下:

var rnd = new Random();

var i = 0;
var z = 0L;

var obs =
    Observable
        .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
        .Select(n => new { n, r = ++i })
        .SelectMany(nr =>
            Observable.Timer(TimeSpan.FromSeconds(rnd.Next(3, 10))), (nr, _) => nr)
        .Do(nr => z = Math.Max(z, nr.n))
        .Where(nr => nr.n >= z)
        .Select(nr => nr.r);
我不喜欢使用。喜欢这样做,但我还想不出其他选择

这就给了这样一种东西:

1 5 8 9 10 11 14 15 16 17 22
这不总是只从最新的请求中获取值吗?我对这个问题的理解是,如果发生了两个对asyncCall的调用,并且结果按调用顺序返回,那么这两个结果都是有效的,但是如果第二个调用首先返回,那么忽略第一个调用的结果。根据您对问题的理解,然后您的假设也是正确的。@LeeCampbell关于如何处理我们已忽略的以前调用的异常的任何建议。我将调用一个异步函数,而不是Observable.TimerTimeSpan.FromSecondsrnd.Next3,如更新后的问题所示。此异步函数可能引发异常。因此,我希望取消以前的异步调用并在不结束IObservable流的情况下处理异常。一般来说,Rx是关于合成序列的,一旦序列出错,它就被视为终端。因此,从这个意义上说,您的用例不受支持。但是,在这种情况下,您可以使用Catch操作符并从中产生适当的值。看看我们如何避免出现异常从使用where子句过滤的旧响应中?@BalrajSingh-那么,如果我用一个偶尔抛出错误的操作替换++I?这是一种可能的情况吗?会发生什么样的错误?我的意思是,我将用对web ser的异步调用来替换它,而不是Observable.TimerTimeSpan.fromssecondsrnd.Next3,10反之,它可能会抛出任何类型的http错误。在这种情况下,我需要取消我发出的所有旧请求,以便它不会通过任何错误,如果它抛出,那么我应该有一种方法来处理它,而不结束我的可观察流。我已经用调用任何异步函数的示例代码更新了该问题,并仅获取最新的响应。Th如果异步函数执行,is可能会引发异常。Enigmativity,我提供了一个答案的实现,没有外部变量和Do语句。在开箱思考方面做得很好。
var rnd = new Random();

var i = 0;
var z = 0L;

var obs =
    Observable
        .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
        .Select(n => new { n, r = ++i })
        .SelectMany(nr =>
            Observable.Timer(TimeSpan.FromSeconds(rnd.Next(3, 10))), (nr, _) => nr)
        .Do(nr => z = Math.Max(z, nr.n))
        .Where(nr => nr.n >= z)
        .Select(nr => nr.r);
1 5 8 9 10 11 14 15 16 17 22
var obs =
    Observable
        .Create<int>(o =>
        {
            var rnd = new Random();
            var i = 0;
            var z = 0L;
            return
                Observable
                    .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
                    .Select(n => new { n, r = ++i })
                    .SelectMany(nr =>
                        Observable.Timer(TimeSpan.FromSeconds(rnd.Next(3, 10))),
                        (nr, _) => nr)
                    .Do(nr => z = Math.Max(z, nr.n))
                    .Where(nr => nr.n >= z)
                    .Select(nr => nr.r)
                    .Subscribe(o);
        });