Самый быстрый многопоточный метод анализа данных последовательного порта C#

#c# #parsing #serial-port

#c# #синтаксический анализ #последовательный порт

Вопрос:

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

Двигатель отправляет данные о местоположении со скоростью до 1000 раз в секунду. Чего я пытаюсь достичь, так это иметь возможность форматировать возвращаемые данные (удаляя из них пробелы, новые строки и т.д.) И анализировать их для извлечения соответствующих данных из полученных строк.

В настоящее время у меня есть обработчик событий data received, который считывает данные, форматирует их, используя серию вызовов метода string.replace, и добавляет их в строку, которая действует как буфер. Затем, используя потоки, я постоянно проверяю буфер по мере его заполнения на наличие определенного разделителя (в моем случае » r»), который обозначает конец одного сообщения от модуля, затем удаляю это сообщение из буфера и печатаю его в расширенном текстовом поле.

С этим подходом связаны две проблемы. Во-первых, поскольку модуль передает данные о местоположении с такой высокой скоростью, буфер заполняется быстрее, чем данные могут быть обработаны потоками. Таким образом, когда я посылаю команду механизму, он действует немедленно, но ответ задерживается на несколько секунд, потому что все предыдущие данные в буфере должны быть обработаны первыми. Во-вторых, наличие двух потоков, выполняющих метод, который реализует структуру while (true), означает, что загрузка процессора резко возрастает, и в течение нескольких секунд вентиляторы на ПК работают на макс.

Есть ли лучший способ обработки данных?

Вот мой код обработчика событий:

  //data recieved event handler
    private void dataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        string tmp;

            tmp = sp.ReadExisting();

            //cut out any unnecessary characters
            tmp = tmp.Replace("n", "");
            tmp = tmp.Replace(",", "r");
            tmp = tmp.Replace(" ", "");

            lock (this)
            {
                //put all received data into the read buffer
                readBuffer  = tmp;
           }

    }
  

Вот метод, который выполняют потоки:

  private void parseBuffer()
    {
        while (true)
        {
            //obtain lock, parse one message from buffer
            lock (this)
            {
                if (readBuffer.IndexOf("r") > 0)
                {
                    String t = readBuffer.Substring(0, readBuffer.IndexOf("r")   1);
                    readBuffer = readBuffer.Replace(t, "");
                    dataReady(this, new CustomEventArgs(t, null));
                }
            }
        }
    }
  

Комментарии:

1. Я нашел проблему. Дело не в том, что программа не может обработать данные в буфере достаточно быстро, а в том, что программа не может выполнить запись в форматированное текстовое поле достаточно быстро. Код, который я использовал, просто делал что-то вроде textBox1.text = myText; . При печати на консоль у меня нет проблем с увеличением буфера быстрее, чем программа обрабатывает данные, как я указывал выше.

Ответ №1:

Ваш parseBuffer будет работать с бешеной скоростью, даже если с момента последней попытки новых данных не появилось.

Вы можете смягчить это с помощью сигнализации.

 private AutoResetEvent waitHandle = new AutoResetEvent(false);
  

Запускает сигнал в dataReceived

 //data recieved event handler
private void dataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    string tmp;

        tmp = sp.ReadExisting();

        //cut out any unnecessary characters
        tmp = tmp.Replace("n", "");
        tmp = tmp.Replace(",", "r");
        tmp = tmp.Replace(" ", "");

        lock (this)
        {
            //put all received data into the read buffer
            readBuffer  = tmp;
            waitHandle.Set(); // <-- tell parseBuffer that new data is available
       }

}
  

дождитесь сигнала в parseBuffer

 private void parseBuffer()
{
    while (true)
    {
        waitHandle.WaitOne(); // <-- waits until there is more data to parse
        //obtain lock, parse one message from buffer
        lock (this)
        {
            if (readBuffer.IndexOf("r") > 0)
            {
                String t = readBuffer.Substring(0, readBuffer.IndexOf("r")   1);
                readBuffer = readBuffer.Replace(t, "");
                dataReady(this, new CustomEventArgs(t, null));
            }
        }
    }
}
  

Комментарии:

1. Это сработало бы, если бы не тот факт, что в буфер может быть помещено сразу несколько сообщений, поэтому при настройке WaitHandle обрабатывается только одно сообщение. Я изменил «if (ReadBuffer. indexOf(«r») > 0)» в while (буфер чтения. indexOf(«r») > 0)».

2. следует избегать блокировки (этого) в соответствии с руководящими принципами

3. Это немного похоже на потоки TCP, вам нужно убедиться, что вы проверяете то, что вы получаете, на потенциальное превышение того, что вы ожидаете, потому что иногда за одну операцию приема может быть получено несколько отправлений

Ответ №2:

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

1) Создайте анализатор конечного автомата, который анализирует входящие данные по одному символу за раз. Когда он создаст полное «сообщение», добавьте его в List<MyMessage> структуру.

2) Используйте виртуализированный ListView или DataGridView для отображения List<MyMessage> .

Ответ №3:

В событии «Полученные данные» считайте входящие данные в виде необработанных байтов и сохраняйте в очереди. Не обрабатывайте данные в обработчике событий. Затем используйте что-то похожее на то, что сказал Альбин, для обработки данных другим методом. Важно разрешить обработчику событий запускаться как можно чаще и не делать больше того, что требуется.

Ответ №4:

В общем случае я бы использовал блокирующую коллекцию между потоком чтения, который считывает исключительно из сокета, и потоком синтаксического анализа.
С точки зрения производительности посмотрите на использование разделения для синтаксического анализа — это намного быстрее, чем замена внутри строки. Вам следует рассмотреть возможность использования регулярного выражения и / или класса StringBuilder — вам следует использовать 1 выражение с альтернативами
Код регулярного выражения будет выглядеть следующим образом:

 string pattern = " |\n|\r";
string replacement = " "; 
regex rgx = new Regex(pattern);
string result = rgx.Replace(input, replacement);
  

Комментарии:

1. Но если моя цель — удалить символ, мне нужно было бы использовать как команду разделения, так и команду объединения. Это все равно было бы быстрее?

2. Почему вы сначала удаляете символы — это действительно дорогостоящая операция — выполняется только в том случае, если у вас реальные проблемы с памятью.