C # - периодическое чтение данных и Thread.Sleep () - PullRequest
3 голосов
/ 28 мая 2010

мое приложение 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...
    }
}

Это решение отлично работает, и все сообщения принимаются правильно. Тем не менее, приложение потребляет слишком много ресурсов, загрузка процессора моего компьютера составляет более 80%. Поэтому я хотел бы уменьшить его.

Благодаря переменной «cycleCount» я знаю, что «скорость цикла» потока составляет около 40 000 циклов в секунду. Это слишком много, потому что мне нужно получить максимум 700 сообщений в секунду. (и устройство имеет буфер для около 100 сообщений, поэтому скорость цикла может быть даже немного ниже)

Я попытался уменьшить скорость цикла, приостановив поток на 1 мс с помощью Thread.Sleep (1); команда. Конечно, это не сработало, и скорость цикла стала около 70 циклов в секунду, что было недостаточно для чтения всех сообщений. Я знаю, что эта попытка была глупой, что для того, чтобы уложить нить в сон, а затем разбудить его, требуется гораздо больше времени, чем 1 мс.

Однако я не знаю, что еще делать: есть ли другой способ замедлить выполнение потока (чтобы уменьшить потребление ресурсов процессора), кроме Thread.Sleep? Или я совершенно не прав и должен ли я использовать что-то другое для этой задачи вместо Thread, может быть Threading.Timer или ThreadPool?

Заранее большое спасибо за все предложения. Это мой первый вопрос, и я новичок в использовании потоков, поэтому, пожалуйста, извините, если это не совсем понятно.

Ответы [ 9 ]

2 голосов
/ 28 мая 2010

Используйте таймер высокой точности.Вы должны попытаться использовать мультимедийный таймер.Это поможет вам снизить нагрузку на процессор.Я не уверен в разрешении, но на постоянное время (если вы не меняете временной интервал, это довольно точно).Вы можете сохранить разрешение 10 мсек, и для каждого вызова таймера вы можете прочитать весь буфер, пока он не станет пустым, или, скажем, 100 раз.Это должно помочь.

0 голосов
/ 28 мая 2010
  1. Убедитесь, что вы полностью понимаете API, предоставляемый вашей DLL. Есть ли способ использовать библиотеку, чтобы уведомить вас о прибытии нового сообщения? Я был бы удивлен, если бы в такой библиотеке, как вы описываете, не было встроенной сигнализации или уведомления.

  2. Если вам необходимо опросить, то при проверке сообщений извлекайте все сообщений. Не просто забрать один, а затем снова заснуть. Получайте все сообщения до тех пор, пока их больше не будет, потому что вы хотите очистить буфер.

0 голосов
/ 28 мая 2010

Я думаю, что вы должны использовать одноэлементный класс с таймером. Этот класс дает вам 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

    }
}
0 голосов
/ 28 мая 2010

Вы можете попробовать Thread.SpinWait :

SpinWait по сути ставит процессор в очень узкий цикл с количеством циклов, указанным параметром iterations. Следовательно, продолжительность ожидания зависит от скорости процессора.

Сравните это с методом сна. Поток, который вызывает Sleep, возвращает оставшуюся часть текущего времени процессора, даже если указанный интервал равен нулю. Задание ненулевого интервала для Sleep удаляет поток из рассмотрения планировщиком потока, пока не истечет временной интервал.

SpinWait предназначен для ожидания без выдачи текущего временного интервала

Он разработан для ситуаций, когда вы знаете, что захотите что-то сделать в очень короткое время, поэтому потеря вашего временного интервала будет чрезмерной.

0 голосов
/ 28 мая 2010

Как насчет использования AutoResetEvent с таймаутом в 1 миллисекунду?

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...
    }
}

РЕДАКТИРОВАТЬ: Определенно проверьте, доступны ли сообщения, как предлагают и другие ответы ...

0 голосов
/ 28 мая 2010

Thread.Sleep также означает отказ от временного интервала вашего потока. Вы вернетесь в поток только после того, как он запланирован снова. Вот почему вы "спите" дольше 1 мс. Смотрите также это сообщение от Питера Ритчи .

Проблема, похоже, в том, что вам нужно активно опрашивать DLL, чтобы получать новые сообщения. Лучшим решением было бы, если бы DLL уведомляла вас о поступлении нового сообщения. Однако вы сказали, что не можете изменить внешнюю DLL.

То, что вы можете попробовать, это уменьшить приоритет потока опроса .

EDIT:

Например, вы можете использовать DispatcherTimer для запуска примерно каждую миллисекунду. DispatcherTimer не будет перегружать ваш процессор, пока событие еще не сработало. Затем в обработчике вы можете обрабатывать сообщения (я полагаю, вы можете сказать, нет ли в данный момент сообщений), пока не останется сообщений для обработки. Вы также должны были бы защититься от повторного входа, то есть попасть в обработчик событий, когда он уже запущен.

Как пример кода:

// 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;
}
0 голосов
/ 28 мая 2010

Я бы сделал:

//...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);
}

Этот способ, вероятно, дает вам максимальную производительность, кроме удара вашего процессора.

0 голосов
/ 28 мая 2010

Что возвращает ReadMessage, если не осталось сообщений?

Вы должны спать в теме, только если она сообщает, что сообщений нет. Сон только в течение 1 мс в этом случае должен помочь.

0 голосов
/ 28 мая 2010

Я бы предложил получить профилировщик или использовать его в VS2010. Вам нужно определить узкое место, а не случайным образом менять некоторые вещи тут и там, чтобы посмотреть, сработает ли это внезапно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...