代码之家  ›  专栏  ›  技术社区  ›  Muzib hardyVeles

如何降低连续触发事件的事件处理频率

  •  1
  • Muzib hardyVeles  · 技术社区  · 6 年前

    我正在学习任务和 async/await 在C语言中。所以请考虑一下我问题的愚蠢性。

    有个活动 DummyEvent 在一个班里。事件处理程序 DummyEventHandler 已订阅此 event 它处理大量CPU绑定的任务,实际上并不需要如此频繁地使用这些任务。

    因为这个原因,如果 虚拟事件 一直被解雇,我想 DummeyEventHandler(DummeyEventHandler) 以较低的频率响应,或在连续性结束时响应。

    所以,我的想法是将大任务提取成一个单独的任务,并使其在进行前延迟500毫秒。延迟结束后,检查同一任务是否已被再次调度(连续事件触发),如果是,则避免进行大的计算。

    以下是我对这一想法的幼稚实现:

    int ReducedCall = 0;
    int TotalCallActual = 0;
    
    protected void DummyEventHandler(object sender, bool arg)
    {
        TotalCallActual++;
        LargeCPUBoundTask(); // there is a green underline here, but I think it's ok, or.. is it?
    }
    
    async Task LargeCPUBoundTask()
    {
        ReducedCall = TotalCallActual;
    
        await Task.Delay(500);
        // if this task is called again in this time, TotalCallActual will inncrease
    
        if (ReducedCall == TotalCallActual)
        {
            // do all the large tasks
            ……
    
            ReducedCall = 0;
            TotalCallActual = 0;
        }
    }
    

    但问题是,我得不到我想要的。线 Task.Delay(500) 实际上并没有等待,或者,如果等待了,就有问题了,因为我经历了错乱。

    有什么更好的主意,或者有什么改进/修正?

    询问任何其他信息。

    谢谢

    3 回复  |  直到 6 年前
        1
  •  2
  •   Peter Bons    6 年前

    你可以利用 Reactive Extensions 这样做:

    void Main()
    {
        var generator = new EventGenerator();
        var observable = Observable.FromEventPattern<EventHandler<bool>, bool>(
                    h => generator.MyEvent += h,
                    h => generator.MyEvent -= h);
    
        observable
            .Throttle(TimeSpan.FromSeconds(1))
            .Subscribe(s =>
            {
                Console.WriteLine("doing something");
            });
    
        // simulate rapid firing event
        for(int i = 0; i <= 100; i++)
            generator.RaiseEvent(); 
    
        // when no longer interested, dispose the subscription  
        subscription.Dispose(); 
    }
    
    public class EventGenerator
    {
        public event EventHandler<bool> MyEvent;
    
        public void RaiseEvent()
        {
            if (MyEvent != null)
            {
                MyEvent(this, false);
            }
        }
    }
    

    这个 Throttle 上面编码的运算符将允许值(事件)每秒变为真。

    所以在上面的代码示例中, 做某事 即使事件被多次激发,也只能打印一次(一秒钟后)。

    编辑
    顺便说一下,绿线的原因是你的任务没有等待。要修复它,请将代码更改为:

    protected async void DummyEventHandler(object sender, bool arg)
    {
        TotalCallActual++;
        await LargeCPUBoundTask(); // there is no more green underline here
    }
    

    不幸的是,这仍然不能解决您的问题,因为无法等待事件,因此如果事件在 LargeCPUBoundTask 仍在运行另一个呼叫 大型公共场所任务 如果你明白我的意思的话,工作是重叠的。换句话说,这就是代码不起作用的原因。

        2
  •  1
  •   Fausto Carias    6 年前

    我将使用计时器事件处理程序而不是您的dummeyEventHandler 只需调整定时器的频率,单位为毫秒秒,就可以了。您可以通过代码创建计时器,而不必将其作为控件添加到窗体中。我认为它在公共控件库中。

    希望这有帮助。祝你好运。

        3
  •  1
  •   Clay Ver Valen    6 年前

    我花了更多的时间思考这个问题,我用我的第一个解决方案所做的假设是,事件是连续不断地发生的,当它可能只是在一段时间内触发一部分时间,然后在 真实的 问题。

    在这种情况下,CPU绑定的任务只会在第一个事件触发时发生,然后如果事件在该CPU绑定的任务完成之前完成触发,则不会处理其余的事件。但你不想处理所有的问题,只想处理“最后一个”问题(不一定是实际的最后一个问题,只需要再处理一个“清理”问题)。

    因此,我更新了我的答案,将出现频繁但间歇的用例(即事件突发然后安静)包括在内,正确的事情会发生,最终运行CPU绑定任务(但每次运行的CPU绑定任务仍不超过1个)。

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    class Program
    {
        static void Main(string[] args)
        {
            Sender s = new Sender();
            using (Listener l = new Listener(s))
            {
                s.BeginDemonstration();
            }
        }
    }
    
    class Sender
    {
        const int ATTEMPTED_CALLS = 1000000;
    
        internal EventHandler frequencyReducedHandler;
        internal int actualCalls = 0;
        internal int ignoredCalls = 0;
    
        Task[] tasks = new Task[ATTEMPTED_CALLS];
    
        internal void BeginDemonstration()
        {
            int attemptedCalls;
            for (attemptedCalls = 0; attemptedCalls < ATTEMPTED_CALLS; attemptedCalls++)
            {
                tasks[attemptedCalls] = Task.Run(() => frequencyReducedHandler.Invoke(this, EventArgs.Empty));
                //frequencyReducedHandler?.BeginInvoke(this, EventArgs.Empty, null, null);
            }
            if (tasks[0] != null)
            {
                Task.WaitAll(tasks, Timeout.Infinite);
            }
            Console.WriteLine($"Attempted: {attemptedCalls}\tActual: {actualCalls}\tIgnored: {ignoredCalls}");
            Console.ReadKey();
        }
    }
    
    class Listener : IDisposable
    {
        enum State
        {
            Waiting,
            Running,
            Queued
        }
    
        private readonly AutoResetEvent m_SingleEntry = new AutoResetEvent(true);
        private readonly Sender m_Sender;
    
        private int m_CurrentState = (int)State.Waiting;
    
        internal Listener(Sender sender)
        {
            m_Sender = sender;
            m_Sender.frequencyReducedHandler += Handler;
        }
    
        private async void Handler(object sender, EventArgs args)
        {
            int state = Interlocked.Increment(ref m_CurrentState);
            try
            {
                if (state <= (int)State.Queued) // Previous state was WAITING or RUNNING
                {
                    // Ensure only one run at a time
                    m_SingleEntry.WaitOne();
                    try
                    {
                        // Only one thread at a time here so
                        // no need for Interlocked.Increment
                        m_Sender.actualCalls++;
                        // Execute CPU intensive task
                        await Task.Delay(500);
                    }
                    finally
                    {
                        // Allow a waiting thread to proceed
                        m_SingleEntry.Set();
                    }
                }
                else
                {
                    Interlocked.Increment(ref m_Sender.ignoredCalls);
                }
            }
            finally
            {
                Interlocked.Decrement(ref m_CurrentState);
            }
        }
    
        public void Dispose()
        {
            m_SingleEntry?.Dispose();
        }
    }