|
Рисование из потока |
|
|
|
Nov 11 2008, 14:18
|
Участник

Группа: Участник
Сообщений: 15
Регистрация: 11-11-08
Пользователь №: 41 540

|
Есть устройство, обменивающееся с компьютером по UART. Протокол вида: компьютер запросил - прибор померил и отдал результат. Для компьютера написана программа на Дельфи. Там в потоке принимается результат, и по факту приема выводится на экран в виде графика с помощью компонента TChart. Программа иногда зависает, иногда бывают аксес виалейшен.
Я хочу переписать программу на С, компилятор возьму MSVS C++. Как следует организовывать работу с графикой и ком-портом? Мне видится, что нужно по событию от таймера проверять состояние флага приема информации, и тогда принимающий поток будет передавать точки графика тому, кто будет рисовать на форме... Как я понял, программа часто зависала из-за того, что графика была "тяжелой", и пока что-то рисовалось в поток успевало придти несколько значений и данные терялись... Возможно, там дело обстояло несколько иначе, но мне кажется, что рисовать прямо из потока не очень хорошо. Интересно было бы увидеть как поступают другие в подобных случаях. И еще вопрос по графике. Есть ли что-то типа TChart в C++, уж очень там удобное масштабирование. Читал, что рисование в DC медленное, но может кто-то пробовал его? По сути мне нужно несколько кривых выводить, правда шкала времени (ось Х) довольно длинная, около суток и более, т.е. ничего хитрого. Но с DC придется отказаться от удобностей типа масштабирования.
|
|
|
|
|
Nov 12 2008, 08:37
|
Участник

Группа: Участник
Сообщений: 15
Регистрация: 11-11-08
Пользователь №: 41 540

|
Цитата В Си ошибок только прибавится. Причем, на порядок. Программу хотят еще и на КПК перенести для работы в полевых условиях. Это одна из причин перехода на Си. Цитата А всего делов - запихивать данные из УАРТа в кольцевой буфер. А рисующий поток из этого кольцевого буфера изымает по мере возможности. И никто не страдает. Надо было мне сразу кусочек кода дать. Вот как у меня сейчас сделано: Поток приема из УАРТа. Код data := buf; SetEvent(Event_new_data); В переменной buf (dword) содержится данные из УАРТа, которые мы копируем в промежуточный буфер. Поток обработки информации. Код WaitForSingleObject(MainForm.Event_new_data, INFINITE); ResetEvent(MainForm.Event_new_k);
Array_data[counter] := MainForm.data; inc(counter); SetLength(Array_data, counter + 1);
// далее сама обработка Ждем новых данных, запихиваем их в массив и выделяем новую ячейку под следующие данные. Массив не кольцевой по причине того, что для данной версии программы нужно хранить всю накопленные данные в памяти. Но это тестовый вариант, поэтому для конечного варианта программы будет сделано что-то типа буфера FIFO. Вообще, предположение о потере пакетов это лишь догадка... Просто когда я отключаю рисование графики, то программа работает стабильно. Цитата В Borland C++ Builder есть TChart. Был бы Билдер для КПК - не знал бы я проблем. Цитата Еще вариант видел: в дополнительном потоке идет работа с com-портом, а приложению постится (WM_USER + x) сообщение с принятыми данными (при этом выделялась память, передавался указатель на буфер с данными; обработчик сообщения данные обрабатывал и память обязательно освобождал). Попробую разобраться с сообщениями в Windows. Цитата Стандартный драйвер Windows умеет организовывать софтверные буферы большого объема на отправку\прием. Этого обычно хвататет чтобы ничего не терять. Забыл вчера написать, что чаще всего виснет сама графика, а обработка данных происходит нормально. Рисование происходит простым добавлением новой точки, например, MainForm.Series1.AddY(high_border[i]). И зависнуть это дело может как на 10 точке добавление, так и на 10000.
|
|
|
|
|
Nov 12 2008, 10:04
|

Знающий
   
Группа: Свой
Сообщений: 648
Регистрация: 11-02-06
Из: Санкт-Петербург
Пользователь №: 14 237

|
Цитата(hadrov @ Nov 11 2008, 17:18)  Как следует организовывать работу с графикой и ком-портом? Мне видится, что нужно по событию от таймера проверять состояние флага приема информации, и тогда принимающий поток будет передавать точки графика тому, кто будет рисовать на форме... ИМХО, таймер здесь лишний. Также мне не нравится идея вывести рисование в отдельный поток для данной задачи. В потоки удобно и нужно разносить вычисления, работающие по отношению друг к другу асинхронно и независимо, либо обслуживающие внешние воздействия, иначе Вы намучаетесь с их синхронизацией, отлавливать ситуации, когда один поток уже удалил объект, а второй осуществляет к нему доступ (access violation) и т. п. Здесь ситуация проста - потоку рисования нечего делать, пока нет новых данных с UART, значит они должны работать синхронно и последовательно - "пришли данные --> надо рисовать", тем более, что рисование происходит заведомо быстрее, чем приходят данные с порта. Поэтому не надо усложнять - я не вижу ничего зазорного в том, чтобы рисовать прямо из потока приёма информации. Для этого удобно хранить в классе, который работает с UART, указатель на объект класса, который всё это рисует, чтобы по факту прихода данных вызвать нужный метод рисования.
--------------------
Сделано в Китае. Упаковано в России.
|
|
|
|
|
Nov 12 2008, 10:46
|
Участник

Группа: Участник
Сообщений: 15
Регистрация: 11-11-08
Пользователь №: 41 540

|
С учетом того, что потребуется версия для КПК, то, как я понимаю, без .NET не обойтись.
|
|
|
|
|
Nov 12 2008, 11:31
|
Гуру
     
Группа: Свой
Сообщений: 3 123
Регистрация: 7-04-07
Из: Химки
Пользователь №: 26 847

|
Цитата(hadrov @ Nov 12 2008, 11:37)  Программу хотят еще и на КПК перенести для работы в полевых условиях. Это одна из причин перехода на Си. Надо было мне сразу кусочек кода дать. Вот как у меня сейчас сделано: Поток приема из УАРТа. Код data := buf; SetEvent(Event_new_data); В переменной buf (dword) содержится данные из УАРТа, которые мы копируем в промежуточный буфер. Поток обработки информации. Код WaitForSingleObject(MainForm.Event_new_data, INFINITE); ResetEvent(MainForm.Event_new_k);
Array_data[counter] := MainForm.data; inc(counter); SetLength(Array_data, counter + 1);
// далее сама обработка Ждем новых данных, запихиваем их в массив и выделяем новую ячейку под следующие данные. Неправильно. Во первых необходим хотя бы небольшой буфер под принимаемый байт. Если поток обработки информации не успеет обработать байт перед тем, как будет готов следующий, то байт потеряется Во вторых, ручной сброс event'а (ResetEvent) может привести к потере одного события - нужно пользовать event с автоматическим сбросом В третих, если 'поток обработки информации' это действительно отдельный поток, и он напрямую добавляет точки в TChart, то это и является источником зависаний - с объектами VCL можно работать ТОЛЬКО из главного потока приложения И в последних - занесение и чтение данных из буфера, разделяемого между 2мя потоками, должно быть синхронизированно (например, критической секцией)
|
|
|
|
|
Nov 12 2008, 11:55
|
Участник

Группа: Участник
Сообщений: 15
Регистрация: 11-11-08
Пользователь №: 41 540

|
Цитата необходим хотя бы небольшой буфер под принимаемый байт. Если поток обработки информации не успеет обработать байт перед тем, как будет готов следующий, то байт потеряется Вы имеете ввиду сделать буфер типа FIFO? Цитата ручной сброс event'а (ResetEvent) может привести к потере одного события - нужно пользовать event с автоматическим сбросом Это Вы за PulseEvent()? Делал и с ее помощью, но почему то автоматический сброс не происходил, вот я и сделал ручной. Попробую еще поковырять... Цитата если 'поток обработки информации' это действительно отдельный поток Угу, данные обрабатываются в отдельном потоке. Цитата он напрямую добавляет точки в TChart, то это и является источником зависаний - с объектами VCL можно работать ТОЛЬКО из главного потока приложения Вот где бы почитать о таких фичах, а то подобные вещи мелькают либо в FAQ'ах или приходят интуитивно, что так делать не стоит. Тогда вопрос, каким образом TChart'у узнать, что я хочу на нем что-то нарисовать. Правильно ли будет ему ждать события от потока обработки данных WaitForSingleObject()? Цитата занесение и чтение данных из буфера, разделяемого между 2мя потоками, должно быть синхронизированно (например, критической секцией) Дык вроде и синхронизировался с помощью event'ов. Попробую с помощью CriticalSection.
|
|
|
|
|
Nov 12 2008, 13:30
|
Участник

Группа: Участник
Сообщений: 15
Регистрация: 11-11-08
Пользователь №: 41 540

|
С Си знаком лучше всего. И я не знаю как быстро работают интерпретаторы Tcl/Tk или VM Java на КПК... имхо, лучше уж Python прикрутить. Форт я весьма давно ковырял. Для начала хотя бы на С# что-то написать. Цитата Был бы Билдер для КПК - не знал бы я проблем. Хм, плохо я искал. Нашел, что Borland Development Studio может под КПК делать программы. Скачаю BDS2009 и попробую ее в первую очередь.
|
|
|
|
|
Nov 12 2008, 18:44
|

Частый гость
 
Группа: Свой
Сообщений: 186
Регистрация: 14-01-06
Из: Украина, г.Харьков
Пользователь №: 13 168

|
Цитата(hadrov @ Nov 11 2008, 16:18)  Есть устройство, обменивающееся с компьютером по UART. Протокол вида: компьютер запросил - прибор померил и отдал результат. Для компьютера написана программа на Дельфи. Там в потоке принимается результат, и по факту приема выводится на экран в виде графика с помощью компонента TChart. Программа иногда зависает, иногда бывают аксес виалейшен. .... Как я понял, программа часто зависала из-за того, что графика была "тяжелой", и пока что-то рисовалось в поток успевало придти несколько значений и данные терялись... Возможно, там дело обстояло несколько иначе, но мне кажется, что рисовать прямо из потока не очень хорошо. Интересно было бы увидеть как поступают другие в подобных случаях. .... То ли я чего-то не понял.... То ли объяснения туманные.... Но это же весьма известный факт - из дополнительных потоков процесса ЗАПРЕЩЕНО непосредственно обращаться к экранным отображаемым объектам (компонентам-наследникам TComponent). Любые обращения к таким объектам обязаны происходить в констексте основного потока приложения или хотя бы синхронизировано с ним (большинство методов почти всех компонентов, кроме специально предназначенных, не являются thread-safe. Конфликт возникает при доступе/изменении одних и тех же данных из разных потоков). Для более-менее удобного избегания конфликтов (если все-таки нужно из потока обращаться к компонентам) придуман метод Synchronize......
|
|
|
|
|
Nov 13 2008, 07:47
|
Гуру
     
Группа: Свой
Сообщений: 3 123
Регистрация: 7-04-07
Из: Химки
Пользователь №: 26 847

|
Цитата(hadrov @ Nov 12 2008, 14:55)  Вы имеете ввиду сделать буфер типа FIFO? Да Цитата Это Вы за PulseEvent()? Делал и с ее помощью, но почему то автоматический сброс не происходил, вот я и сделал ручной. Попробую еще поковырять... Нет, это я за 2й параметр в функции CreateEvent (ManualReset), он должен быть FALSE Цитата Тогда вопрос, каким образом TChart'у узнать, что я хочу на нем что-то нарисовать. Правильно ли будет ему ждать события от потока обработки данных WaitForSingleObject()? Воспользоваться synchronized процедурой Цитата Дык вроде и синхронизировался с помощью event'ов. Попробую с помощью CriticalSection. Нужно и то и другое - с помощью event'ов сообщать что появились новые данные, с помощью CriticalSection защищать доступ к этим данным (FIFO) из 2х разных потоков
|
|
|
|
|
Nov 17 2008, 23:14
|
Участник

Группа: Участник
Сообщений: 15
Регистрация: 11-11-08
Пользователь №: 41 540

|
Цитата Воспользоваться synchronized процедурой Ага, теперь понял откуда ноги ростут. Эта процедура из TThread. При создании модуля с классом TThread и написано, что нельзя рисовать из потока, кроме как используя метод synchronize. Позже попробую написать его потомок, а пока я потоки реализованы с помощью API. В этом случае есть возможность рисовать из потока в главном окне? Цитата Нужно и то и другое - с помощью event'ов сообщать что появились новые данные, с помощью CriticalSection защищать доступ к этим данным (FIFO) из 2х разных потоков Что бы не действовать методом научного тыка приведу краткий алгоритм, а то не уверен, что правильно Вас понял. Поток приема из УАРТа. Код 1. Ждем стартовый байт. (WaitCommEvent(), WaitForSingleObject()) 2. Входим в критическую секцию. (EnterCriticalSection()) 3. Записываем данные в буфер. (ReadFile()) 4. Выходим из критической секции. (LeaveCriticalSection()) 5. Устанавливаем событие в сигнальное состояние (событие с автосбросом) (SetEvent()) 6. Переходим к п. 1. Поток обработки данных Код 1. Ждем наступления события. (WaitForSingleObject()) 2. Входим в критическую секцию. 3. Извлекаем данные из буфера. 5. Выходим из критической секции. 6. Переходим к п. 1. И как после извлечении данных из буфера и выхода из критической секции вставить пункт "Рисуем в главном окне", чтобы он отработал нормально?
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|