15

两者都以System.Timers.TimerSystem.Threading.Timer请求的间隔相当大的间隔发射。例如:

new System.Timers.Timer(1000d / 20);

产生一个每秒触发 16 次的计时器,而不是 20 次。

为了确保没有太长的事件处理程序的副作用,我编写了这个小测试程序:

int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

// Test System.Timers.Timer
foreach (int frequency in frequencies)
{
    int count = 0;

    // Initialize timer
    System.Timers.Timer timer = new System.Timers.Timer(1000d / frequency);
    timer.Elapsed += delegate { Interlocked.Increment(ref count); };

    // Count for 10 seconds
    DateTime start = DateTime.Now;
    timer.Enabled = true;
    while (DateTime.Now < start + TimeSpan.FromSeconds(10))
        Thread.Sleep(10);
    timer.Enabled = false;

    // Calculate actual frequency
    Console.WriteLine(
        "Requested frequency: {0}\nActual frequency: {1}\n",
        frequency, count / 10d);
}

输出如下所示:

要求:5 赫兹;实际:4,8 Hz
要求:10 Hz;实际:9,1 Hz
要求:15 Hz;实际:12,7 Hz
要求:20 Hz;实际:16 Hz
请求:30 Hz;实际:21,3 Hz
要求:50 Hz;实际:31,8 Hz
要求:75 Hz;实际:63,9 Hz
要求:100 Hz;实际:63,8 Hz
要求:200 Hz;实际:63,9 Hz
要求:500 Hz;实际:63,9 赫兹

实际频率与请求频率的偏差高达 36%。(而且显然不能超过 64 Hz。)鉴于微软推荐这个计时器是因为它的“更高的准确性” System.Windows.Forms.Timer,这让我感到困惑。

顺便说一句,这些不是随机偏差。它们每次都是相同的值。另一个计时器类的类似测试程序System.Threading.Timer显示了完全相同的结果。

在我的实际程序中,我需要每秒精确地收集 50 个样本的测量值。这还不需要实时系统。每秒获得 32 个样本而不是 50 个样本是非常令人沮丧的。

有任何想法吗?

@Chris:你是对的,间隔似乎都是 1/64 秒左右的整数倍。顺便说一句,在事件处理程序中添加 Thread.Sleep(...) 没有任何区别。考虑到使用线程池,这是有道理System.Threading.Timer的,因此每个事件都在空闲线程上触发。

4

10 回答 10

23

如果您使用 winmm.dll,您可以使用更多的 CPU 时间,但有更好的控制。

这是修改为使用 winmm.dll 计时器的示例

const String WINMM = "winmm.dll";
const String KERNEL32 = "kernel32.dll";

delegate void MMTimerProc (UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2);

[DllImport(WINMM)]
static extern uint timeSetEvent(
      UInt32            uDelay,      
      UInt32            uResolution, 
      [MarshalAs(UnmanagedType.FunctionPtr)] MMTimerProc lpTimeProc,  
      UInt32            dwUser,      
      Int32             fuEvent      
    );

[DllImport(WINMM)]
static extern uint timeKillEvent(uint uTimerID);

// Library used for more accurate timing
[DllImport(KERNEL32)]
static extern bool QueryPerformanceCounter(out long PerformanceCount);
[DllImport(KERNEL32)]
static extern bool QueryPerformanceFrequency(out long Frequency);

static long CPUFrequency;

static int count;

static void Main(string[] args)
{            
    QueryPerformanceFrequency(out CPUFrequency);

    int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

    foreach (int freq in frequencies)
    {
        count = 0;

        long start = GetTimestamp();

        // start timer
        uint timerId = timeSetEvent((uint)(1000 / freq), 0, new MMTimerProc(TimerFunction), 0, 1);

        // wait 10 seconds
        while (DeltaMilliseconds(start, GetTimestamp()) < 10000)
        {
            Thread.Sleep(1);
        }

        // end timer
        timeKillEvent(timerId);

        Console.WriteLine("Requested frequency: {0}\nActual frequency: {1}\n", freq, count / 10);
    }

    Console.ReadLine();
}

static void TimerFunction(UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2)
{
    Interlocked.Increment(ref count);
}

static public long DeltaMilliseconds(long earlyTimestamp, long lateTimestamp)
{
    return (((lateTimestamp - earlyTimestamp) * 1000) / CPUFrequency);
}

static public long GetTimestamp()
{
    long result;
    QueryPerformanceCounter(out result);
    return result;
}

这是我得到的输出:

Requested frequency: 5
Actual frequency: 5

Requested frequency: 10
Actual frequency: 10

Requested frequency: 15
Actual frequency: 15

Requested frequency: 20
Actual frequency: 19

Requested frequency: 30
Actual frequency: 30

Requested frequency: 50
Actual frequency: 50

Requested frequency: 75
Actual frequency: 76

Requested frequency: 100
Actual frequency: 100

Requested frequency: 200
Actual frequency: 200

Requested frequency: 500
Actual frequency: 500

希望这可以帮助。

于 2009-04-01T00:44:45.280 回答
5

这些类不适合实时使用,并且受制于 Windows 等操作系统的动态调度特性。如果您需要实时执行,您可能希望查看一些嵌入式硬件。我不确定 100%,但我认为 .netcpu 可能是芯片上较小的 .NET 运行时的实时版本。

http://www.arm.com/markets/emerging_applications/armpp/8070.html

当然 - 您需要评估这些间隔的准确性有多重要,因为附加到它们的代码将在非实时操作系统上执行。除非这当然是一个纯粹的学术问题(在这种情况下 - 是的,这很有趣!:P)。

于 2009-01-06T13:50:12.497 回答
4

看起来您的实际计时器频率是 63.9 Hz 或其整数倍。

这意味着约 15 毫秒(或其整数倍,即 30 毫秒、45 毫秒等)的定时器分辨率。

这是基于“滴答”整数倍的计时器,这是可以预料的(例如,在 DOS 中,“滴答”的值为 55 毫秒/18 赫兹)。

我不知道为什么你的滴答计数是 15.65 mec 而不是 15 ms。作为一个实验,如果您在计时器处理程序中睡几个毫秒会怎样:我们是否会看到滴答之间有 15 毫秒,而您的计时器处理程序中每个滴答有 0.65 毫秒?

于 2009-01-06T14:01:43.767 回答
3

Windows(以及因此在其上运行的 .NET)是一种抢先式多任务操作系统。任何给定的线程都可以随时被另一个线程停止,如果抢占线程的行为不正常,您将无法在需要或需要时获得控制权。

简而言之,这就是为什么您不能保证获得准确的时间,以及为什么 Windows 和 .NET 不适合某些类型的软件平台。如果生命处于危险之中,因为您无法在需要的时候获得控制权,请选择不同的平台。

于 2009-01-06T14:01:52.347 回答
3

嗯,实际上我得到了不同的数字,最高可达 100 Hz,有一些很大的偏差,但在大多数情况下,更接近请求的数字(使用最新的 .NET SP 运行 XP SP3)。

System.Timer.Timer 是使用 System.Threading.Timer 实现的,因此这解释了为什么您会看到相同的结果。我想定时器是使用某种调度算法等实现的(它是内部调用,也许看看Rotor 2.0可能会对此有所了解)。

我建议使用另一个线程(或其组合)调用睡眠和回调来实现一种计时器。虽然不确定结果。

否则,您可能会查看多媒体计时器(PInvoke)。

于 2009-01-06T14:14:46.253 回答
2

如果您确实需要跳转到实时环境,我过去曾在需要确定性采样(来自自定义串行设备)时使用 RTX,并且非常幸运。

http://www.pharlap.com/rtx.htm

于 2009-01-06T14:36:26.210 回答
2

这是使用多媒体计时器的计时器的一个很好的实现 http://www.softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html

于 2010-02-12T02:34:32.973 回答
1

定时器频率关闭的原因有很多。以硬件为例,同一个线程正忙于另一个处理,依此类推......

如果您想要更准确的时间,请使用 System.Diagnostics 命名空间中的 Stopwatch 类。

于 2009-01-06T13:42:12.963 回答
0

部分问题是计时器有两个延迟要考虑。这可能会有所不同,具体取决于计时器在操作系统中的实现方式。

  1. 您要求等待的时间
  2. #1 发生与进程进入调度队列之间的时间。

计时器对#1 有很好的控制,但对#2 几乎没有控制。它可以向操作系统发出信号,表示它想再次运行,但操作系统可以在需要时随意唤醒它。

于 2009-01-06T14:18:39.113 回答
0

根据您的评论,您根本不应该使用计时器。您应该使用带有秒表的循环来检查间隔和自旋锁,这样您就不会丢失量子。

于 2009-04-01T00:48:50.400 回答