问题
我正在使用.Net 4.5创建一个基于 Windows 7 的 C# WPF应用程序,它的一个主要功能是调用某些函数,这些函数与具有一组用户定义的周期时间的自定义硬件接口。例如,用户可以选择每 10 或 20 毫秒调用两个函数,每 500 毫秒调用另一个函数。用户可以选择的最小循环时间为 1 毫秒。
起初,时间似乎是准确的,并且根据需要每 1 毫秒调用一次函数。但我们后来注意到,大约1-2%的时间不准确,有些函数被调用时仅延迟了 5 毫秒,而其他函数可能延迟了 100 毫秒。即使循环时间大于 1 毫秒,我们也面临线程在它应该调用外部函数的时间休眠的问题(20 毫秒的函数可能会延迟 50 毫秒,因为线程正在休眠并且没有调用该函数)
经过分析,我们得出结论,这些延迟是零星的,没有明显的模式,并且这些延迟背后的主要原因可能是操作系统调度和线程上下文切换,换句话说,我们的线程并没有像我们需要的那样一直处于唤醒状态.
由于 Windows 7 不是 RTOS,我们需要确定是否可以以某种方式解决此问题。但我们确实知道这个问题在 Windows 上是可以解决的,因为我们使用具有类似功能的其他工具可以满足最大 0.7 毫秒容错的时序约束。
我们的应用程序是多线程的,最多同时运行大约 30 个线程,其当前的峰值 CPU 使用率约为 13%
尝试的解决方案
我们尝试了很多不同的东西,主要是使用秒表计时器测量计时,并且IsHighResolution是正确的(使用了其他计时器,但我们没有发现太大差异):
创建一个单独的线程并赋予它高优先级
结果:无效(使用可怕的Thread.Sleep()
,没有它并使用连续轮询)使用 C# 任务(线程池)
结果:几乎没有改进使用具有 1ms 周期的多媒体计时器
结果:无效或更糟,多媒体计时器在唤醒操作系统时是准确的,但操作系统可能会选择运行另一个线程,没有 1ms 保证,但即便如此,延迟有时可能会更大创建了一个单独的独立 C# 项目,其中仅包含一个 while 循环和秒表计时器
结果:大多数情况下,即使在微秒内,精度也很好,但偶尔线程会休眠重复第 4 点,但将进程优先级设置为 Realtime/High
结果:非常好的数字,几乎没有一条消息有明显的延迟。
结论:
从前面我们发现我们有 5 种可能的行动方案,但我们需要有此类问题经验的知识渊博的人来为我们指明正确的方向:
我们的工具可以进行优化,并以某种方式管理线程以确保 1 毫秒的实时要求。也许优化的一部分是将工具的进程优先级设置为高或实时,但这似乎不是一个明智的决定,因为用户可能同时使用多个其他工具。
我们将我们的工具分为两个进程,一个包含 GUI 和所有非时间关键操作,另一个包含最少量的时间关键操作并将其设置为高/实时优先级,并使用 IPC(如 WCF)来进程之间的通信。这可以通过两种方式使我们受益
由于发生的操作少得多,其他进程出现饥饿的可能性更小。
该进程将具有更少的线程,因此(更少或没有)线程休眠的可能性
注意:接下来的两点将涉及内核空间,请注意我对内核空间和编写驱动程序的信息很少,所以我可能对如何使用它做出了一些错误的假设。
在内核空间中创建一个驱动程序,该驱动程序每 1ms 使用较低级别的中断来触发一个事件,该事件迫使线程在进程中执行其指定的任务。
将时间关键组件移至内核空间,与程序主体的任何接口都可以通过 API 和回调来完成。
也许所有这些都无效,我们可能需要使用 Windows RTOS 扩展,如 IntervalZero RTOS 平台?
问题本身
我正在寻找两个答案,我希望它们得到良好资源的支持。
这真的是一个线程和上下文切换问题吗?还是我们一直在遗漏一些东西?
5 个选项中的哪一个可以保证解决这个问题,如果有几个,哪个是最简单的?如果这些选项都不能解决它,那有什么可以解决的呢?请记住,我们基准测试过的其他工具在 windows 上确实达到了所需的计时精度,并且当 CPU 处于重负载下时,100,000 次中的一到两次可能会关闭不到 2 毫秒,这是非常可以接受的。