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

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

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

В настоящее время у меня есть обработчик полученных данных, который считывает данные, форматирует их с помощью серии вызовов метода 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));
                }
            }
        }
    }

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


Ответы (4)


Ваш 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));
            }
        }
    }
}
person Albin Sunnanbo    schedule 13.05.2011
comment
Это бы сработало, если бы не тот факт, что в буфер может помещаться сразу несколько сообщений, поэтому при установке waitHandle обрабатывается только одно сообщение. Я изменил if (readBuffer.IndexOf(\r) › 0) на while (readBuffer.IndexOf(\r) › 0). - person isometrik; 13.05.2011
comment
блокировки (этого) следует избегать в соответствии с рекомендациями - person Marino Šimić; 14.05.2011
comment
Это немного похоже на потоки TCP, вам нужно убедиться, что вы проверяете то, что вы получаете, на потенциал большего, чем вы ожидаете, потому что иногда за одну операцию приема может быть получено несколько отправлений. - person Paul Farry; 14.05.2011

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

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

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

person Tergiver    schedule 13.05.2011

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

person dbasnett    schedule 13.05.2011

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

string pattern = " |\\n|\\r";
string replacement = " "; 
regex rgx = new Regex(pattern);
string result = rgx.Replace(input, replacement);
person weismat    schedule 13.05.2011
comment
Но если моя цель — удалить символ, мне нужно будет использовать как команду разделения, так и команду конкатенации. Это все равно будет быстрее? - person isometrik; 13.05.2011
comment
Почему вы сначала удаляете символы - это действительно дорогая операция - это делается только в том случае, если у вас есть реальные проблемы с памятью. - person weismat; 14.05.2011