【问题标题】:How to throttle the speed of an event without using Rx Framework如何在不使用 Rx 框架的情况下限制事件的速度
【发布时间】:2014-01-28 08:35:33
【问题描述】:

我想限制事件的速度,如何在不使用 Microsoft Rx 框架的情况下实现这一点。我在 Rx 的帮助下完成了这项工作。但我正在尝试的是,我需要根据时隙限制 Map 的 View changed 事件。是否可以在不使用 Rx 的情况下实现相同的功能。

我不允许使用 Rx,我必须保持二进制大小尽可能小。

【问题讨论】:

  • 您至少应该通过解释 Rx 出了什么问题以及解决方案需要具备哪些属性来限制问题,否则问题就太宽泛了。我现在的第一反应是:如果 Rx 有效(而且做得如此优雅),那么为什么要重新发明轮子呢?
  • 问题是由于某些原因,我无法将 Rx 库包含到我的项目中。所以仅仅为了实现这一点,添加这么大的库似乎很奇怪。
  • 投反对票有什么理由吗?我要求另一种方法。它有什么问题?
  • 如果我是你,我会重新考虑使用能够满足你需要的库的问题。它小于 1Mb。在 SO 中破解无错误代码以重现相同的功能对我来说似乎不是有效地利用时间。至少,如果遇到困难,请自己尝试一下并显示一些代码。
  • 只需使用您自己的计时器。当您收到更改的事件时启用它。当它滴答作响并做你的事情时禁用它。

标签: c# windows-8 windows-runtime system.reactive c#-5.0


【解决方案1】:

例如,如果您的事件是 EventHandler<EventArgs> 类型,则此方法有效。它为你的事件处理程序创建了一个被限制的包装器:

private EventHandler<EventArgs> CreateThrottledEventHandler(
    EventHandler<EventArgs> handler, 
    TimeSpan throttle)
{   
    bool throttling = false;
    return (s,e) =>
    {
        if(throttling) return;              
        handler(s,e);
        throttling = true;
        Task.Delay(throttle).ContinueWith(_ => throttling = false);
    };
}

像这样附加:

this.SomeEvent += CreateThrottledEventHandler(
    (s,e) => Console.WriteLine("I am throttled!"),
    TimeSpan.FromSeconds(5));

不过,如果您以后需要将其与-= 断开连接,您应该存储从CreateThrottledEventHandler 返回的处理程序。

【讨论】:

  • 您能否演示一下如何将其附加到现有事件中。
  • 在附加示例中编辑。
  • 任何不添加 Await for Task.Delay() 的原因
  • 没有。只是对 .NET 4.0 的人友好。
  • 虽然我只在 Window 8 桌面上进行了测试,但 Task.Delay 在任何平台 AFAIK 上都不同步——至少在默认任务调度程序上不同步;在您的情况下,一定还有其他事情发生。显然,要使上述代码正常工作,必须异步重置“throttle”标志,否则事件委托链将被阻塞。
【解决方案2】:

我认为以下要求在“节流”事件处理程序中是必不可少的:

  • 立即引发 first 事件。
  • 在限制期内发生的后续事件被忽略
  • 一旦限制期结束,保证在限制期内发生的最后事件。

考虑到这些要求,之前接受的答案并不令人满意;它正确地满足了前两个要求,但不能保证最终会引发最后一个事件。我认为这一点特别重要,因为高频引发的事件通常代表“状态变化”和/或“用户请求”;我们总是希望收到状态或用户交互变化的最新更新。

为了满足所有这些要求,我创建了自己的通用“ThrottledEventHandler”类。

public class ThrottledEventHandler<TArgs>
    where TArgs : EventArgs
{
    private readonly EventHandler<TArgs> _innerHandler;
    private readonly EventHandler<TArgs> _outerHandler;
    private readonly Timer _throttleTimer;

    private readonly object _throttleLock = new object();
    private Action _delayedHandler = null;

    public ThrottledEventHandler(EventHandler<TArgs> handler, TimeSpan delay)
    {
        _innerHandler = handler;
        _outerHandler = HandleIncomingEvent;
        _throttleTimer = new Timer(delay.TotalMilliseconds);
        _throttleTimer.Elapsed += Timer_Tick;
    }

    private void HandleIncomingEvent(object sender, TArgs args)
    {
        lock (_throttleLock)
        {
            if (_throttleTimer.Enabled)
            {
                _delayedHandler = () => SendEventToHandler(sender, args);
            }
            else
            {
                SendEventToHandler(sender, args);
            }
        }
    }

    private void SendEventToHandler(object sender, TArgs args)
    {
        if (_innerHandler != null)
        {
            _innerHandler(sender, args);
            _throttleTimer.Start();
        }
    }

    private void Timer_Tick(object sender, EventArgs args)
    {
        lock (_throttleLock)
        {
            _throttleTimer.Stop();
            if (_delayedHandler != null)
            {
                _delayedHandler();
                _delayedHandler = null;
            }
        }
    }

    public static implicit operator EventHandler<TArgs>(ThrottledEventHandler<TArgs> throttledHandler)
    {
        return throttledHandler._outerHandler;
    }
}

用法如下所示:

myObject.MyFrequentlyRaisedEvent += new ThrottledEventHandler(MyActualEventHandler, TimeSpan.FromMilliseconds(50));

【讨论】:

  • 对我来说似乎过度设计,在我的尝试中,一个刻度处理程序似乎就足够了。猜猜它与节流事件处理程序有关;个人喜好可能是使用辅助类而不是添加处理程序。
【解决方案3】:

这是一个 Throttle 方法,灵感来自 James World 的 CreateThrottledEventHandler 方法,它模仿了 Rx Throttle/Debounce 运算符的行为。它仅传播在dueTime 不活动期之后发生的事件。这意味着,如果源事件快速连续引发,并且它们之间没有大于dueTime 的时间间隔,则不会传播任何事件。

/// <summary>Ignores events that are followed by another event within
/// a specified relative time duration.</summary>
public static EventHandler<TEventArgs> Throttle<TEventArgs>(
    EventHandler<TEventArgs> handler,
    TimeSpan dueTime)
{
    System.Threading.Timer timer = null;
    return (s, e) =>
    {
        var newTimer = new System.Threading.Timer(
            _ => handler(s, e), null, dueTime, Timeout.InfiniteTimeSpan);
        var previousTimer = Interlocked.Exchange(ref timer, newTimer);
        previousTimer?.Dispose();
    };
}

使用示例:

public event EventHandler<int> SomeEvent;

//...

this.SomeEvent += Throttle<int>((s, e) =>
{
    Console.WriteLine($"Received: {e}");
}, TimeSpan.FromSeconds(1.0));

【讨论】:

  • 嗨@noseratio!是的,在 Rx 中有一个 Throttle 运算符可用。文档确实过时了!
  • @noseratio 根据Throttle 的文档,忽略来自可观察序列的值,该序列后跟在指定源、dueTime 和调度程序的到期时间之前的另一个值。 AFAIK 这意味着每个接收到的值都不会立即发出。在接收和发出一个值之间总是有一个等于dueTime 的延迟,除非当前缓冲的值被抢占,在这种情况下它只是被丢弃。我回答的Throttle 方法试图复制这种行为。 TBH 我不知道Debounce 在其他 Rx 实现中是如何工作的。
  • 只是为了澄清一下,比如说,我想仅在用户停止输入并且自上次击键后经过 1 秒时才调用拼写检查 Web api。那会反弹。通过节流,只要用户继续输入,无论如何都会每 1 秒调用一次 web api。我想我需要对 Rx .NET 进行试验,看看它的 Throttle 实际在做什么 :)
  • @noseratio AFAIK 它正在做去抖动,这是您的情况下理想的功能。但是通过实验验证它肯定不会受到伤害。 ?
  • ...而 RxJS 拥有所有这些:auditauditTimedebouncedebounceTimedebounceTimesamplesampleTimethrottlethrottleTime。嫉妒! ?
猜你喜欢
  • 1970-01-01
  • 2011-03-13
  • 1970-01-01
  • 2010-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多