C# - 定期数据读取和Thread.Sleep()

时间:2010-05-28 11:47:57

标签: c# multithreading

我的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?

提前感谢所有建议。这是我在这里的第一个问题,我是使用线程的初学者,所以如果不够清楚,请原谅我。

9 个答案:

答案 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)

  1. 确认您完全了解DLL正在公开的API。有没有办法使用该库来通知您新邮件的到来?如果您所描述的图书馆没有内置信号或通知,我会感到惊讶。

  2. 如果您必须轮询,则在检查邮件时,检索所有邮件。不要只检索一个,然后再次入睡。获取所有消息,直到没有更多消息,因为你想要缓冲清除。

答案 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。