订阅不同时间间隔的正确方式

时间:2017-11-09 07:01:45

标签: .net system.reactive

我正在构建我的第一个Reactive.NET应用程序,并且正在努力管理两组observable。

首先,我在两个集合中轮询服务器。我有两个“响铃”时间(更快更慢):一个每秒发射一个,每15秒发射一个。我注意到,如果服务器需要一段时间来处理其中一个响铃中的一个呼叫,它将立即“赶上”并在呼叫完成后连续触发事件(通过超时或其他方式)。因此,如果我每秒开火并且有30秒的延迟,则一旦延迟完成(假设没有其他进一步的延迟),订阅将通过触发30次来恢复。这不是首选,因为我希望将对服务器的调用保持在最低限度,因此每个环的每个时间帧严格遵守。也就是说,如果有30秒的延迟,我不希望一旦延迟过去就用电话轰炸服务器。

我使用Sample解决了这个问题。而且,实际上,一切都运行良好,除了现在应用程序启动时,外环有一个延迟,直到15秒才看到任何输出。使用1秒钟的戒指,我使用StartsWith(-1)来解决这个问题,但我似乎无法用外(慢)环来解决这个问题。

这是我的代码:

var fast = TimeSpan.FromSeconds(1);
var slow = TimeSpan.FromSeconds(15);
var application = Observable.Interval(fast)
                            .Sample(fast)
                            .StartWith(-1)
                            .Timestamp()
                            .Window(slow)
                            .Sample(slow)
                            .Subscribe(window =>
                                        {
                                            // Outer (slower) ring: ...

                                            window.Subscribe(x =>
                                                            {
                                                                // Inner (faster) ring: ...
                                                            });
                                        });

所以,真的,问题是:

  1. 这是在Rx中创建两个不同间隔的订阅时间环的正确/首选方式吗? (这很重要,因为我可以看到额外的时间响应,例如30秒,1分钟,5分钟等等。我想确保我做对了。)
  2. 如果没有,这样做的最佳/首选方式是什么?
  3. 我如何确保外环立即发射并且在第一次尝试时不需要15秒才会发射?
  4. 编辑:

    基于@ yohanmishkin的回答,这是我正在使用的代码:

    var poll = Observable.Interval(TimeSpan.FromSeconds(1));
    var lessFrequentPoll = Observable.Interval(TimeSpan.FromSeconds(15));
    
    poll.Subscribe(o => application.UpdateFrequent(o));
    lessFrequentPoll.Subscribe(o => application.UpdateLessFrequent(o));
    
    using (new CompositeDisposable(poll, lessFrequentPoll))
    {
        // ...
    }
    

    正如答案中所提到的,当我第一次启动Rx时,我确实使用了这个,但我最初在想(由于我对Rx的原始理解),对于每个订阅,我必须嵌套using上下文,并希望避免这种情况。所以,如果我最终得到5个“响铃”时间,我会有5个嵌套using,这对我来说并不好看。使用CompositeDisposable可以缓解这种情况。

2 个答案:

答案 0 :(得分:1)

在这种情况下,您可能需要创建两个单独的observable并分别订阅它们的应用程序。

以下是一个例子:

CombineLatest

如果你仍然希望将两个区间组合成一个流,Rx提供了大量的操作符(Merge,Zip,CombineLatest ......)。

在您的情况下,您可能需要查看WithLatestFromWithLatestFrom。您可以看到它们如何工作的可视化here (CombinedLatest)here (WithLatestFrom)

示例可能是使用var combinedPoll = poll .WithLatestFrom( lessFrequentPoll, (pollEvent, lessFrequentPollEvent) => new CombinedEvent(pollEvent, lessFrequentEvent) ); combinedPoll.Subscribe(o => { application.UpdateFrequent(o.FrequentEvent); application.UpdateLessFrequent(o.LessFrequentEvent); }); 创建一个新流,该流发出一些对象,该对象组合了您的应用程序随后可以订阅的两个区间可观察对象。

Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
If Forms!DASHBOARD!NavigationSubform.Form!rptMain.chkWaBox.Value = True Then
Detail.Visible = True
End If
End Sub

进一步阅读

Combining sequences

答案 1 :(得分:0)

我发现这种事情对Rx来说有点棘手。似乎订阅的作品不应该真正具有修改序列本身的副作用。但是,这就是你所需要的,因为如果工作运行缓慢(服务很慢),你需要减少轮询频率。

认为 IScheduler.Schedule()的各种递归重载旨在支持这种事情。请参阅http://www.introtorx.com/Content/v1.0.10621.0/15_SchedulingAndThreading.html

这个想法是这样的:

private static IObservable<int> PollServer(IScheduler scheduler)
{
    return Observable.Create<int>(
        observer =>
        {
            var cts = new CancellationTokenSource();
            var token = cts.Token;

            // Make initial call immediately
            var scheduled = scheduler.Schedule(
                TimeSpan.Zero,
                async action =>
                {
                    try
                    {
                        var res = await CallServer(token);
                        observer.OnNext(res);

                        // Trigger another iteration after poll delay
                        action(TimeSpan.FromSeconds(1));
                    }
                    catch (Exception ex)
                    {
                        observer.OnError(ex);
                    }
                });

            // Discontinue polling when observable is disposed
            return () =>
            {
                cts.Cancel();
                scheduled.Dispose();
            };
        });
}

private static async Task<int> CallServer(CancellationToken token)
{
    // Remote service call
    await Task.Delay(100);
    return 42;
}

这显然只提供了你的“慢速戒指”的代码,但希望它可以让你知道你需要什么。