| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C++ Builder > Программа выполняет длительный процесс |
| Автор: Нитонисе 22.9.2012, 13:10 |
| Программа долго выполняет вычисления. Время может исчисляться минутами. При этом во время вычисления она как бы зависает. Хотя в конце концов вычисления заканчиваются и программа возвращается в штатный режим. Мне хотелось бы реализовать в окне главной формы индикатор процесса, который бы постепенно заполнялся, при этом бы давалась текстовая информация что программа делает в данную секунду. В свзяи с этим появились два вопроса: 1. Время вычисления в общем случае неизвестно и не ясно как можно охарактеризовать объем вычислений. Отсюда возникает вопрос - а из каких соображений определять текущий процент выполнения работы? Как это делается обычно, когда общий объем неизвестен? 2. Хотелось бы рядом с прогресс-баром иметь кнопку Отмена, чтобы можно было остановить вычисления в любой момент. Но как я уже выше отметил - программа "зависает" при работе и на команды не реагирует. Так как все же в этом случае можно реализовать подобную функцию? |
| Автор: artsb 22.9.2012, 13:39 |
| Во-первых, нужно всю работу вынести в отдельный поток. тогда и окно не будет зависать и кнопка будет работать, и прогрессбар будет отрисовываться, и работу можно будет всегда прервать. Во-вторых, если продолжительность операции не известна, то можно сделать прогрессбар в стиле бегающих квадратиков (есть такое свойство у прогрессбара). Начиная с Vista, отображаются не квадратики, а свечение. Ну или разобраться с выполняемой работой, и всё таки. как-нибудь рассчитать время работы. Что за работа то? |
| Автор: Нитонисе 22.9.2012, 16:05 | ||
Никогда такого не делал. Можно элементарный пример?
Просто показывать, что программа считает - наверное не очень удобно. Хотелось бы, чтобы отображалась текущая позиция расчета. Суть вычислений - расчет строительной конструкции. Алгоритм весьма ветвистый. Какие-то блоки считаются, какие-то нет. Все зависит от исходных данных. Некоторые блоки могут очень долго считаться, но при других исходных эти же блоки считаются быстро. То есть в одном случае какой-то блок может занять 90% расчетного времени, а вдругом случае - всего 10%. Похоже что в таких условиях прогрессбар с отображение текущего выполнения не лучшая идея? Возможно стоит разбить весь расчет на некоторые структурные части и просто каким либо образом (текстовым или графическим) информировать пользователя в каком блоке сейчас идет вычисление. |
| Автор: artsb 22.9.2012, 18:16 | ||||
Копайте в сторону TThread. Добавить в проект поток можно через меню: 1. File -> New -> Other... 2. C++Builder Projects -> C++Builder Files 3. Thread Object А дальше изучайте справку и ищите примеры в интернете, т.к. в двух словах описать не получится.
Да. Но бывает, что выхода нет.
Тоже идея. Можно ещё разбить всю вашу работу на этапы. Например, если у вас получилось 23 этапа, то вы так и выставляйте в Progressbar'е: Min = 0, Max = 23. И потом, после каждого этапа делайте Position++ |
| Автор: Dem_max 22.9.2012, 18:25 |
| http://rxlib.ru/WinLesson/bles2_1.htm |
| Автор: Нитонисе 22.9.2012, 20:51 |
| Спасибо, буду разбираться. |
| Автор: Нитонисе 23.9.2012, 12:10 | ||
| Создал класс, производный от TThread - TMyThread. Добавил в него функцию DoCalculate(){Form1->Calculate). В методе Execute прописал всего одну строчку - Synchronize(DoCalculate). В основной программе в обработчике клика на кнопку, запускающую расчет, написал такой код.
В результате сам расчет-то выполняется, но основная форма программы все также недоступна во время его выполнения. То есть например отменить выполнение расчета возможности нет. Почему так? |
| Автор: Dem_max 23.9.2012, 15:36 |
| Да не может быть такого, ты явно что то не то делаешь, но мы не телепаты. Но могу предположить что ты в DoCalculate делаешь длительный расчет, который опять же выполняется в главном потоке окна что и было до этого, вопрос почему конкретно не выполняешь расчет в самом потоке ?? |
| Автор: artsb 23.9.2012, 21:28 | ||||
Dem_max, правильно говорит. Этим вызовом:
вы запускаете на выполнение расчёты. Причём, запускаете работу с синхронизацией с VCL. Вот всё и виснет. Если в методе Calculate() нет работы с VCL, то можете из потока просто вызвать
без синхронизации. Тогда всё должно быть нормально. Если же в этом методе есть обращение к VCL, то переносите весь код в поток, НО синхронизировать нужно ТОЛЬКО работу с VCL. Иначе, опять всё повиснет. |
| Автор: Dem_max 24.9.2012, 02:52 |
| ТС я же тебе ссылку давал там разжован материал для самых тупых. |
| Автор: Riviero 24.9.2012, 09:38 | ||
|
| Автор: Нитонисе 24.9.2012, 10:35 | ||
Перенести весь код в поток - это ж надо менять области видимости переменных. А что значит "НО синхронизировать нужно ТОЛЬКО работу с VCL"? Вот класс TForm - это класс VCL? Если у меня расчетом непосредственно занимается другой класс TCalculate, являющийся членом класса TForm, значит ли это, что обращение к методам TCalculate я работаю с VCL? |
| Автор: artsb 24.9.2012, 10:49 | ||||||
Нет, если ваш класс не потомок какого-нибудь VCL-класса. Например,
Вот обращение к свойствам или методам VCL-класса, это и есть то, что я подразумевал под работой с VCL. Т.е. в данном случае, это изменение свойства Caption для Label1. Вот эту операцию и нужно синхронизировать. Добавлено через 1 минуту и 15 секунд
Конечно. Придётся как-то приспосабливаться. А, вообще, это нужно было заранее продумывать. Тогда и переделывать ничего бы не пришлось. |
| Автор: DarkProg 24.9.2012, 11:04 | ||||
Ну и зашибись, если всё вынесено в отдельный класс. Просто запихайте класс в поток, там его создайте, выполните все расчёты, а результат верните в форму используя синхронизацию.
Ну если используете глобальные переменные, то я вам не завидую. В остальном просто надо подумать где и что надо передать. Самый просто способ это передать копии всех необходимых значений в поток, чтобы меньше надо было делать синхронизаций. |
| Автор: Нитонисе 24.9.2012, 12:43 | ||
Вот это точно |
| Автор: Нитонисе 26.9.2012, 10:51 | ||||
| Переработал код, чтобы все вычисление шло в отдельном потоке, но что-то не работает. В основной программе есть функция Calculate(), кторая выполняет расчет. Сначала она проверяет исходные данные и наполняет структуру с данными, необходимыми для расчета. Затем запускает отдельный поток, где сам расчет и происходит.
Вот так принципиально выглядит класс TMyThread
Проблема в том, что в поток структура S передается без данных. То есть вычислить res невозможно. Но как так? Ведь я же передал адрес нужной структуры данных из основного потока. |
| Автор: xvr 26.9.2012, 11:02 | ||
Вам нужно скопировать к себе структуру при старте Thread'а, т.к. ваша оригинальная структура выходит из области видимости и разрушается сразу после запуска thread'а |
| Автор: Нитонисе 26.9.2012, 13:22 | ||
Так ведь я в потоке работаю не с самой структурой, а с ее адресом. Этот адрес я и передаю при создании потока. Ведь эта операция корректна? |
| Автор: Dem_max 26.9.2012, 14:38 |
| Ты вышел за пределы Calculate() и все твой объект TMyStruct S разрушен, а поток TMyThread(false,&S); запускается уже после выхода из Calculate() как ты думаешь какой адрес получит поток ??? |
| Автор: xvr 26.9.2012, 16:14 |
Передача адреса корректна, а вот обращение по нему - нет. Dem_max вполне наглядно все расписал |
| Автор: Нитонисе 26.9.2012, 16:56 | ||
Значит TMyStruct должна быть глобальной переменной? Ну или хотябы членом класса формы. |
| Автор: DarkProg 26.9.2012, 17:33 | ||
Нет, ты в поток должен отдать значения из этой структуры, либо сделать указатель на структуру, выделить под указатель память, и потом передать этот указатель, только выделенную память потом надо будет как-то освободить(либо в деструкторе, либо в конце вычислений). |
| Автор: Нитонисе 26.9.2012, 17:52 | ||
Ну я это и имел ввиду. Объявить глобальную переменную - указатель на структуру, в функции Calculate() выделить память для TMyStruct, заполнить структуру данными, передать указатель классу TMyThread и там использовать эти данные в вычислениях. А освободить память видимо надо в деструкторе TMyThread? Насколько это будет корректно при завершении работы потока штатно (то есть когда вычисления закончены) или внештатно (пользователь нажал кнопку "Отмена", прервав расчет)? |
| Автор: DarkProg 26.9.2012, 18:58 | ||
Да не нужна вам глобальная переменная! Указатель тем и хорош, что указатель вы отдадите потоку, и поток с ним сможет работать. И ничего ему не будет. В нынешнем варианте у вас объявляется переменная, которая "умирает" как только заканчивается выполнение процедуры, ну и собсно результат известен, а нужен указатель. Т.е. по сути сейчас компилятор выделяет память и освобождает её, и ему невдомёк, что адрес переданный в поток трогать нельзя. Используя указатель типа указатель на структуру вы уже сами начинаете управлять памятью, выделяя её, а переменная-указатель всего лишь является хранилищем для адреса и её разрушение после выхода из процедуры не будет иметь никакого значения для памяти которая выделена под структуру и значений которые там хранятся. И не путайте глобальную переменную и локальную переменную. Как реализуете, можно в деструкторе, можно в конце метода Execute, главное не забыть это сделать. Правильнее будет в деструкторе наверное в данном случае, особенно учитывая возможность прерывания вычислений. |
| Автор: xvr 26.9.2012, 19:01 | ||
Сделайте просто локальную копию. Вот так -
|
| Автор: artsb 26.9.2012, 20:33 |
Тогда нужно не забыть перегрузить оператор присваивания. А то всплывёт потом "что-нибудь"... |
| Автор: xvr 26.9.2012, 21:08 | ||
Ну если в TMyStruct кроме интов входит еще что-нибудь (например AnsiString), то это "что-нибудь" всплывет сразу |