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