3

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

但是,我不想在每秒内均匀地开火x

因此,假设n= 100, 25 可能会在前 300 毫秒内触发,然后在接下来的 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;
        }

    }
4

5 回答 5

0

创建一个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;
}
于 2012-08-30T18:15:56.310 回答
0

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

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

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

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

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

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

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

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

于 2012-08-30T18:46:37.620 回答
0

要创建随机事件时间数组:

  1. 选择事件之间的最小时间间隔,并用所有可能的事件时间填充数组。例如,如果您选择 1ms 的粒度,那么数组将有 1000 个值:0、0.001、0.002、...、0.999。
  2. 每秒对数组进行打乱以随机化元素的顺序。
  3. 使用数组的前 n 个元素作为事件的触发时间。
于 2012-08-30T18:00:37.737 回答
0

我建议将 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 的延迟将增加一倍,但总和不受影响。

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

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

希望这可以帮助!

于 2012-08-30T18:37:15.177 回答
0
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;
            }
        }
    }    
}
于 2012-08-30T19:50:33.240 回答