【问题标题】:Generate an event X numbers of times per second but not evenly spaced within each second每秒生成事件 X 次,但每秒内间隔不均匀
【发布时间】:2012-08-30 17:51:51
【问题描述】:

我想写一个类来触发一个事件x 每秒预定义的次数调用它n

但是,我希望x 在每一秒内均匀地发射。

所以,假设n = 100,前 300 毫秒可能会触发 25 个,然后在接下来的 600 毫秒内再触发 50 个,在剩余的 100 毫秒内触发最后 25 个。

理想情况下,我希望粒度比上面介绍的更好,每秒内的间距范围更大。

我想知道我是否能够创建一个具有定义毫秒数的数组以及要触发多少个事件。然后使用循环和Stopwatch 类来确定是否应该触发为该毫秒航点定义的事件。

问题是,每秒计算数组的速度是否足够快,每一秒都应该有随机间距。

显然,事件需要是异步的,以避免被连接到它的东西延迟。

有人遇到过类似的需求吗?

更新

我想我至少会把我最初的努力放在这里。

所以我发现有足够的分辨率来检查你当前处于哪一毫秒以及哪一秒。基本上,我每毫秒重建间隔的每一秒都会在数组中获得一个条目,每个条目表示该毫秒触发事件的次数。

我的问题是间距...我需要一种更好的方法来尝试聚集事件计数,目前这个 jsut 似乎将它们以 0、1 或 2 的间距均匀分布。

public delegate void EmptyEventDelegate();
    public class RandEvent
    {
        public event EmptyEventDelegate OnEvent = delegate { };
        private bool running = false;
        Random r = new Random();

        private int eventsPS;

        public RandEvent(int eventsPS = 1)
        {
            this.eventsPS = eventsPS;
        }

        public void Start()
        {
            running = true;
            Task.Factory.StartNew(() =>
                {
                    Run();
                });
        }

        private void Run()
        {
            var sw = new Stopwatch();
            sw.Start();

            int currentSecond = 0;
            int[] eventCount = BuildEventSpacing();

            while(running)
            {
                if (currentSecond != sw.Elapsed.Seconds)
                {
                    currentSecond = sw.Elapsed.Seconds;
                    eventCount = BuildEventSpacing();
                }
                else
                {
                    for(int i = 0; i < eventCount[sw.Elapsed.Milliseconds]; i++)
                        OnEvent();

                }
            }

            sw.Stop();
        }

        private int[] BuildEventSpacing()
        {
            var array = new int[1000];

            for (int i = 0; i < eventsPS; i++)
            {
                array[r.Next(0, 999)]++;
            }

            return array;
        }

        public void Stop()
        {
            running = false;
        }

    }

【问题讨论】:

标签: c#


【解决方案1】:

创建随机事件时间数组:

  1. 选择事件之间的最小时间间隔,并用所有可能的事件时间填充数组。例如,如果您选择 1ms 的粒度,则该数组将有 1000 个值:0、0.001、0.002、...、0.999。
  2. 每秒随机排列数组以随机化元素的顺序。
  3. 使用数组的前 n 个元素作为事件的触发时间。

【讨论】:

    【解决方案2】:

    创建一个Timer 并在Tick 事件中做任何需要做的事情。此外,使用以下方法(在Tick 事件的附加处理程序中)每次更改间隔。最好每次都提供相同的 Random 实例传递给方法,而不是提供新实例。

    private static double millisecondsPerSecond = 1000.0;
    /// <summary>
    /// Method used to determine how long you would wait for the event to fire next
    /// </summary>
    /// <param name="averageFiresPerSecond">The approximate number of times the event should occur per second.</param>
    /// <param name="variance">How much variance should be allowed, as a percentage.  i.e. a variance of 0.1 would mean that 
    /// the delay will be +/- 10% of the exact rate.</param>
    /// <param name="generator">A randon number generator.</param>
    /// <returns>The number of milliseconds to wait for the next event to fire.</returns>
    public double GetNextDelay(int averageFiresPerSecond, double variance, Random generator)
    {
        double randomFactor = ((generator.NextDouble() * 2) * variance);
        return (millisecondsPerSecond / averageFiresPerSecond) * randomFactor;
    }
    

    【讨论】:

      【解决方案3】:

      假设您想要 100 个总和为 1 的数字。您可以执行以下操作:

      1. 生成 100 个介于 0 和 1 之间的随机数(即Random.NextDouble)并存储在一个列表中。
      2. 将数字相加。
      3. 将列表中的每个数字除以总和。

      您现在有一个包含 100 个数字的列表,总和为 1.0。

      现在,创建一个一次性计时器,其间隔是列表中的第一个值。当它触发时,处理程序完成其工作,然后再次设置一次性计时器,其间隔等于列表中的下一个值。

      除非它不能完美地工作,因为它没有考虑处理滴答声和重新设置计时器所需的时间。因此,您必须跟踪该延迟并相应地调整下一个刻度。但即使这样也不会完美。

      正如我在评论中所说,您将很难让任何 .NET 计时器每秒为您提供超过 60 个滴答声。 充其量,您可以预期计时器滴答之间有 15 毫秒。即使您使用自定义计时器实现,也会遇到一些麻烦,因为计时器滴答声不会准确按时发生。当然,随着计时器间隔的减少,问题会变得更糟。

      无论如何,您无法让 Windows 计时器为您提供超过 1 毫秒的分辨率,因此您必须调整我上面描述的方法以提供大于或等于 0.001 的数字.

      您使用Stopwatch 的想法可以与我创建事件间隔的方法一起使用。但是,请理解循环将在单个内核上消耗接近 100% 的 CPU 时间。即使那样,它也可能不会准确准时为您提供事件,因为其他更高优先级的任务可能会导致您的循环被换出。

      【讨论】:

        【解决方案4】:

        我建议将 TimeSpan 对象的集合初始化为相等且总和为 1 秒。确切的值将由您的n 确定。

        从那里,您可以按您喜欢的任何值偏移对或组,并且您的总数仍然是一秒。比如……

        var offset = new TimeSpan.FromMilliseconds(10); // 10ms offset
        timeSpans[0] += offset;
        timeSpans[1] -= offset;
        

        通过这种方式,您可以在不影响总和的情况下移动它们。如果你需要让它们早点来,晚点少,那么你可以做类似的事情......

        timeSpans[0] -= offset;
        timeSpans[1] -= offset;
        timeSpans[2] += offset;
        timeSpans[2] += offset;
        

        这将使索引 0 和 1 的延迟更短,索引 2 的延迟将增加一倍,但总和不受影响。

        唯一要记住的是,任何时间跨度都不应小于 0s,并且总和应始终为 1s,那么你就是金子。分发 TimeSpan 对象后,您可以使用它们在触发事件x 之间暂停。你会想要改变(随机化?)你的偏移量几次,每次你改变它时,选择新的 timeSpans(随机?)来应用偏移量。在这个过程结束时,它应该是相当混乱的间隔。

        如果太抽象,我可以提供更详细的代码示例。 :)

        希望这会有所帮助!

        【讨论】:

        • 或者你可以使用TimeSpan.FromMilliseconds(10)。而且,正如我所指出的,.NET 计时器不太可能为您提供 10 毫秒的分辨率。
        • 间距是我现在最感兴趣的。正如@JimMischel 所提到的,我的核心之一正在以 80% 的速度运行。
        【解决方案5】:
        using System;
        using System.Text;
        using System.Collections.Generic;
        using System.Linq;
        using Microsoft.VisualStudio.TestTools.UnitTesting;
        using System.Diagnostics;
        
        namespace TestProject1
        {
            [TestClass]
            public class Test
            {
                [TestMethod]
                public void TestSteps()
                {   
                    var random = new Random();
                    for (int i = 0; i < 20000; i++)
                    {
                        int numberOfEvents = random.Next(1,1000);
                        var generator = new RandomTimeStepGenerator(numberOfEvents);
                        var stopwatch = new Stopwatch(); 
                        stopwatch.Start();
                        var steps = generator.MillisecondDeltas;
                        Assert.AreEqual(numberOfEvents, steps.Count);
                        var sum = generator.MillisecondDeltas.Sum();
                        Assert.AreEqual(1000.0,sum,0.1);
                        Assert.IsTrue(stopwatch.ElapsedMilliseconds<10);
                    }
        
                }        
            }
        
            public class RandomTimeStepGenerator
            {
                private readonly int _numberOfEvents;
                const int timeResolution = 10000;
        
                public RandomTimeStepGenerator(int numberOfEvents)
                {
                    _numberOfEvents = numberOfEvents;
                }
        
                public int NumberOfEvents
                {
                    get { return _numberOfEvents; }
                }
        
                public List<double> MillisecondDeltas
                {
                    get
                    {
                        var last=0;
                        var result = new List<double>();
                        var random = new Random();
        
                        for (var i = 0; i < timeResolution && result.Count < _numberOfEvents; i++)
                        {
                            var remainingEvents = _numberOfEvents - result.Count;
                            var remainingTime = timeResolution - i;
                            if(remainingEvents==1) // make sure the last event fires on the second
                            {
                                result.Add((timeResolution - last) / 10.0);
                                last = i;  
                            }
                            else if (remainingTime <= remainingEvents) // hurry up and fire you lazy !!! your going to run out of time 
                            {
                                result.Add(0.1);
                            }
                            else
                            {
                                double probability = remainingEvents / (double)remainingTime;
                                int next = random.Next(0,timeResolution);                        
                                if ((next*probability) > _numberOfEvents)
                                {
                                    result.Add((i - last)/10.0);
                                    last = i;
                                }
                            }                   
                        }                 
                        return result;
                    }
                }
            }    
        }
        

        【讨论】:

        • 你确定这真的有效吗?它是否总是生成_numberOfEvents 事件?间隔总和是否总是timerResolution 毫秒?
        • 是的,这就是测试的目的。 :o) 它总是生成事件的数量,并且它应该总是总和为 1000 毫秒或 1 秒,而不是 timerResolution,因为它表示 1/10 毫秒。如果您想要每秒超过 1000 个事件,它确实变得不可靠。如果您要求 0 个事件,则它无法通过测试,因为它不能汇总任何内容。
        • 顺便说一句,如果你想要TimeSpan,只需使用IEnumerable&lt;TimeSpan&gt; timeSpans = generator.MillisecondDeltas.Select(TimeSpan.FromMilliseconds);
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-09-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-17
        • 2021-04-22
        相关资源
        最近更新 更多