我的C#应用程序从特殊USB设备读取数据。数据被读作所谓的“消息”,每个消息都有24个字节。每秒必须读取的消息量可能不同(最大频率非常高,每秒约700条消息),但应用程序必须全部读取它们。
读取消息的唯一方法是调用函数“ReadMessage”,它返回从设备读取的一条消息。该函数来自外部DLL,我无法修改它。
我的解决方案:我有一个单独的线程,它在程序运行期间一直在运行,它的唯一工作就是循环读取消息。然后在主应用程序线程中处理收到的消息。
“阅读线程”中执行的功能如下:
private void ReadingThreadFunction() {
int cycleCount;
try {
while (this.keepReceivingMessages) {
cycleCount++;
TRxMsg receivedMessage;
ReadMessage(devHandle, out receivedMessage);
//...do something with the message...
}
}
catch {
//... catch exception if reading failed...
}
}
此解决方案工作正常,所有邮件都已正确接收。但是,应用程序占用了太多资源,我的计算机CPU运行率超过80%。所以我想减少它。
由于“cycleCount”变量,我知道线程的“循环速度”大约是每秒40 000个循环。这是不必要的太多,因为我需要获得最多700个消息/秒。 (并且设备具有大约100条消息的缓冲区,因此循环速度甚至可以更低一些)
我尝试通过Thread.Sleep(1)暂停线程1 ms来降低循环速度;命令。当然,这不起作用,循环速度变为大约70周/秒,这还不足以阅读所有消息。我知道这个尝试很愚蠢,让线程进入睡眠然后唤醒他需要的时间超过1毫秒。
但是,我不知道还能做什么:除了Thread.Sleep之外,还有其他一些方法可以减慢线程执行速度(减少CPU消耗)吗?或者我完全错了,我应该使用不同的东西来代替Thread,也许是Threading.Timer或ThreadPool?
提前感谢所有建议。这是我在这里的第一个问题,我是使用线程的初学者,所以如果不够清楚,请原谅我。
答案 0 :(得分:2)
使用高精度的计时器。您应该尝试使用多媒体计时器。它将帮助您减少CPU使用量。我不确定是否重新开始,但是对于一个恒定的时间(你不再改变时间间隔,它是相当准确的)。您可以保持10毫秒的分辨率,并且对于每次调用计时器,您可以读取整个缓冲区,直到它为空或说100次。这应该有所帮助。
答案 1 :(得分:0)
我建议使用分析器,或者使用VS2010中的分析器。你需要确定瓶颈,而不是随意改变这里和那里的一些东西,看看它是否会突然发挥作用。
答案 2 :(得分:0)
如果没有留言,ReadMessage会返回什么?
如果它告诉你没有消息,你应该只睡眠线程。在这种情况下睡1分钟只能做到这一点。
答案 3 :(得分:0)
Thread.Sleep也意味着放弃你的线程的时间片。只有在再次安排后,您才会返回该线程。这就是为什么你“睡觉”的时间超过1毫秒。另请参阅this post by Peter Ritchie。
问题似乎是你必须主动轮询DLL以获取新消息。更好的解决方案是让DLL在新消息到达时通知您。但是,您说您无法修改外部DLL。
你可以尝试减少priority of the polling thread。
编辑:
例如,您可以使用DispatcherTimer大约每毫秒触发一次。事件尚未触发时,DispatcherTimer不会破坏您的CPU。然后,在处理程序中,您可以处理消息(我假设您可以判断当前是否没有消息),直到没有消息要处理。您还必须防止重入,即在事件处理程序运行时进入事件处理程序。
代码示例:
// Creating the DispatcherTimer
var dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(1);
dispatcherTimer.Start();
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
if (this.ProcessingEvents)
return;
this.ProcessingEvents = true;
var messagesLeft = true;
while (messagesLeft)
{
TRxMsg receivedMessage;
ReadMessage(devHandle, out receivedMessage);
// Process message
// Set messagesLeft to false if no messages left
}
this.ProcessingEvents = false;
}
答案 4 :(得分:0)
使用超时为1毫秒的AutoResetEvent怎么样?
private void ReadingThreadFunction() {
try {
var autoReset = new AutoResetEvent(false);
autoReset.WaitOne(1);
TRxMsg receivedMessage;
ReadMessage(devHandle, out receivedMessage);
//...do something with the message...
}
}
catch {
//... catch exception if reading failed...
}
}
编辑:绝对检查消息是否可用,以及其他答案建议......
答案 5 :(得分:0)
我认为你应该做的是使用带有计时器的单例类。这个类的作用是每秒给你700条消息,如果它需要多一点,它每秒等待并重试700条消息。您可以在另一个线程上实例化它。单例是线程安全的。
// Queue Start
ProcessQueue.Instance.Start();
// Queue Stop
ProcessQueue.Instance.Stop();
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Diagnostics;
namespace Service.Queue
{
/// <summary>
/// Process Queue Singleton Class
/// </summary>
class ProcessQueue : IDisposable
{
private static readonly ProcessQueue _instance = new ProcessQueue();
private bool _IsProcessing = false;
int _maxMessages = 700;
public bool IsProcessing
{
get { return _IsProcessing; }
set { _IsProcessing = value; }
}
~ProcessQueue()
{
Dispose(false);
}
public static ProcessQueue Instance
{
get
{
return _instance;
}
}
/// <summary>
/// Timer for Process Queue
/// </summary>
Timer timer = new Timer();
/// <summary>
/// Starts Process Queue
/// </summary>
public void Start()
{
timer.Elapsed += new ElapsedEventHandler(OnProcessEvent);
timer.Interval = 1000;
timer.Enabled = true;
}
/// <summary>
/// Stops Process Queue
/// </summary>
public void Stop() {
_IsProcessing = false;
timer.Enabled = false;
}
/// <summary>
/// Process Event Handler
/// /// </summary>
private void OnProcessEvent(object source, ElapsedEventArgs e)
{
if (!_IsProcessing)
{
_IsProcessing = true;
for (int i = 0; i < _maxMessages; i++)
{
TRxMsg receivedMessage;
ReadMessage(devHandle, out receivedMessage);
}
_IsProcessing = false;
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
timer.Dispose();
}
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
答案 6 :(得分:0)
确认您完全了解DLL正在公开的API。有没有办法使用该库来通知您新邮件的到来?如果您所描述的图书馆没有内置信号或通知,我会感到惊讶。
如果您必须轮询,则在检查邮件时,检索所有邮件。不要只检索一个,然后再次入睡。获取所有消息,直到没有更多消息,因为你想要缓冲清除。
答案 7 :(得分:-1)
您可以尝试Thread.SpinWait:
SpinWait本质上将处理器放入一个非常紧凑的循环中,循环计数由iterations参数指定。因此,等待的持续时间取决于处理器的速度。
将此与Sleep方法进行对比。即使指定的时间间隔为零,调用Sleep的线程也会产生剩余的当前处理器时间片。为Sleep指定非零间隔会从线程调度程序中删除该线程,直到时间间隔结束。
SpinWait设计为等待而不产生当前时间片
它适用于你知道你想在很短的时间内做某事的情况,所以失去你的时间片会过度。
答案 8 :(得分:-2)
我愿意:
//...do something with the message...
// If the message is null, it indicates that we've read everything there is to be read.
// So lets sleep for 1ms to give the buffer chance to receive a few extra messages.
If (theMessage == null) // Buffer is empty as we failed to read a new message
{
Thread.Sleep(1);
}
这种方式可能会给你带来最大的性能,而不是锤击你的CPU。