在没有onBackpressureLatest的情况下处理Rx.NET中的背压

时间:2017-02-14 21:41:21

标签: c# f# system.reactive reactive-programming

我需要在Rx.NET中实现以下算法:

  1. stream获取最新项目,或者如果没有新项目,则等待新项目不会阻止。只有最新项目很重要,其他项目可以删除。
  2. 将项目输入SlowFunction并打印输出。
  3. 从步骤1开始重复。
  4. 天真的解决方案是:

    let PrintLatestData (stream: IObservable<_>) =
        stream.Select(SlowFunction).Subscribe(printfn "%A")
    

    但是,此解决方案不起作用,因为平均stream发出比SlowFunction更快的项目可以消耗它们。由于Select不会丢弃项目,而是尝试按从最旧到最新的顺序处理每个项目,因此当程序运行时,正在发出和打印的项目之间的延迟将增加到无穷大。只应从流中获取最新的最新项目,以避免这种无限增长的背压。

    我搜索了文档并在RxJava中找到了一个名为onBackpressureLatest的方法,根据我的理解,我会按照上面的描述进行操作。但是,该方法在Rx.NET中不存在。如何在Rx.NET中实现它?

4 个答案:

答案 0 :(得分:8)

我认为你想使用像ObserveLatestOn这样的东西。它有效地用单个值和一个标志替换传入事件的队列。

詹姆斯世界在这里发表了关于它的博客http://www.zerobugbuild.com/?p=192

这个概念在GUI应用程序中被大量使用,这些应用程序无法信任服务器在其上推送数据的速度。

您还可以在Reactive Trader中查看实施https://github.com/AdaptiveConsulting/ReactiveTrader/blob/83a6b7f312b9ba9d70327f03d8d326934b379211/src/Adaptive.ReactiveTrader.Shared/Extensions/ObservableExtensions.cs#L64 以及解释ReactiveTrader https://leecampbell.com/presentations/#ReactConfLondon2014

的支持性演示文稿

要清楚这是一个减载算法,而不是背压算法。

答案 1 :(得分:1)

同步/异步建议可能会有所帮助,但是,假设慢速函数总是慢于事件流,使得异步可能允许您以(最终在线程池上观察)并行处理(最终) )只是耗尽线程或通过上下文切换添加更多延迟。它听起来不像是我的解决方案。

我建议你看一下开源Rxx&#39; Introspective&#39;由Dave Sexton编写的运算符。这些可以改变您最新的缓冲区/节流周期,因为队列由于消费者的缓慢而备份。如果慢速功能突然变快,它根本不会缓冲。如果它变慢,它会缓冲它更多。 您必须检查是否有来自&#39;的最新信息。键入,或只是修改现有的以满足您的需求。例如。使用缓冲区,只需取缓冲区中的最后一项,或进一步增强内部只存储最新项。 Google&#39; Rxx&#39;,你可以在某个地方的Github上找到它。

一种更简单的方法,如果&#39;慢功能的时间&#39;是可以预测的,只是简单地限制你的流量超过这个时间。显然,我并不是指标准的rx&#39;油门,而是让我们通过更新而不是旧的更新。这里有很多解决方案可以解决这类问题。

答案 2 :(得分:1)

同样的问题也发生在我前面,我没有找到一个内置的运算符来做到这一点。所以我写了自己的,我称之为Latest。实现并不简单,但发现它在我当前的项目中非常有用。

它的工作原理如下:当观察者忙于处理先前的通知时(当然是在它自己的线程上),它会将最后一次最多n个通知排队(n> = 0)和OnNext s观察者一旦变得空闲。所以:

  • Latest(0):只观察观察者空闲时到达的物品
  • Latest(1):始终遵守最新的
  • Latest(1000)(例如):通常会处理所有项目,但是如果某些事情被困在线上,而不是错过一些而不是OutOfMemoryException
  • Latest(int.MaxValue):不要错过任何项目,但要在生产者和消费者之间实现负载平衡。

您的代码将是:stream.Latest(1).Select(SlowFunction).Subscribe(printfn "%A")

签名如下:

/// <summary>
/// Avoids backpressure by enqueuing items when the <paramref name="source"/> produces them more rapidly than the observer can process.
/// </summary>
/// <param name="source">The source sequence.</param>
/// <param name="maxQueueSize">Maximum queue size. If the queue gets full, less recent items are discarded from the queue.</param>
/// <param name="scheduler">Optional, default: <see cref="Scheduler.Default"/>: <see cref="IScheduler"/> on which to observe notifications.</param>
/// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="maxQueueSize"/> is negative.</exception>
/// <remarks>
/// A <paramref name="maxQueueSize"/> of 0 observes items only if the subscriber is ready.
/// A <paramref name="maxQueueSize"/> of 1 guarantees to observe the last item in the sequence, if any.
/// To observe the whole source sequence, specify <see cref="int.MaxValue"/>.
/// </remarks>
public static IObservable<TSource> Latest<TSource>(this IObservable<TSource> source, int maxQueueSize, IScheduler scheduler = null)

实施太大,无法在此发布,但如果有人感兴趣,我很乐意分享它。让我知道。

答案 3 :(得分:1)

您可以sample知道SlowFunction可以处理的时间段TestScheduler ts = new TestScheduler(); Observable<Long> stream = Observable.interval(1, TimeUnit.MILLISECONDS, ts).take(500); stream.sample(100, TimeUnit.MILLISECONDS, ts).subscribe(System.out::println); ts.advanceTimeBy(1000, TimeUnit.MILLISECONDS); 。这是java中的一个例子:

98
198
298
398
498
499
sample

sample不会导致背压并始终抓取流中的最新值,因此它符合您的要求。此外,499不会发送两次相同的值(从上面可以看到C#仅打印一次)

我认为这是一个有效的F# / static IDisposable PrintLatestData<T>(IObservable<T> stream) { return stream.Sample(TimeSpan.FromMilliseconds(100)) .Select(SlowFunction) .Subscribe(Console.WriteLine); } 解决方案:

let PrintLatestData (stream: IObservable<_>) =
    stream.Sample(TimeSpan.FromMilliseconds(100))
        .Select(SlowFunction)
        .Subscribe(printfn "%A")
public class ApplicationSignInManager : SignInManager<ApplicationUser, long>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
            : base(userManager, authenticationManager)
        {
        }

        public async Task<SignInStatus> PasswordSystemSignInAsync(string userName, string password, bool IsPersistent, bool shouldLockout, bool IsAdministrative, string securityCode = null)
        {
            var user = await UserManager.FindByNameAsync(userName);
            if(user != null)
            {
                bool passwordCheck = await UserManager.CheckPasswordAsync(user, password);
                if (passwordCheck)
                {
                    var signInUser = await user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager, IsAdministrative);
                    if (signInUser.IsAuthenticated)
                    {
                        return SignInStatus.Success;
                    }
                    return SignInStatus.Failure;
                }
                return SignInStatus.Failure;
            }
            return SignInStatus.Failure;
        }
}