Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C++ Builder > Программа выполняет длительный процесс


Автор: Нитонисе 22.9.2012, 13:10
Программа долго выполняет вычисления. Время может исчисляться минутами. При этом во время вычисления она как бы зависает. Хотя в конце концов вычисления заканчиваются и программа возвращается в штатный режим. Мне хотелось бы реализовать в окне главной формы индикатор процесса, который бы постепенно заполнялся, при этом бы давалась текстовая информация что программа делает в данную секунду. В свзяи с этим появились два вопроса:
1. Время вычисления в общем случае неизвестно и не ясно как можно охарактеризовать объем вычислений. Отсюда возникает вопрос - а из каких соображений определять текущий процент выполнения работы? Как это делается обычно, когда общий объем неизвестен? 
2. Хотелось бы рядом с прогресс-баром иметь кнопку Отмена, чтобы можно было остановить вычисления в любой момент. Но как я уже выше отметил - программа "зависает" при работе и на команды не реагирует. Так как все же в этом случае можно реализовать подобную функцию?

Автор: DarkProg 22.9.2012, 13:35
Цитата(Нитонисе @  22.9.2012,  14:10 Найти цитируемый пост)
1. Время вычисления в общем случае неизвестно и не ясно как можно охарактеризовать объем вычислений. Отсюда возникает вопрос - а из каких соображений определять текущий процент выполнения работы? Как это делается обычно, когда общий объем неизвестен? 

Ну можно сделать как у майкрософта, когда осталось 5 секунд, которые длятся вечность  smile 
А вообще я в таком случае просто вывожу окошко, где написано что-то вроде "Жвите, я тут считаю..." и обычно просто плавно менются последовательно 1, 2, 3 точки и по новой.

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

Автор: artsb 22.9.2012, 13:39
Во-первых, нужно всю работу вынести в отдельный поток. тогда и окно не будет зависать и кнопка будет работать, и прогрессбар будет отрисовываться, и работу можно будет всегда прервать.
Во-вторых, если продолжительность операции не известна, то можно сделать прогрессбар в стиле бегающих квадратиков (есть такое свойство у прогрессбара). Начиная с Vista, отображаются не квадратики, а свечение. Ну или разобраться с выполняемой работой, и всё таки. как-нибудь рассчитать время работы. Что за работа то?

Автор: Нитонисе 22.9.2012, 16:05
Цитата(artsb @  22.9.2012,  13:39 Найти цитируемый пост)
Во-первых, нужно всю работу вынести в отдельный поток.

Никогда такого не делал. Можно элементарный пример?

Цитата(artsb @  22.9.2012,  13:39 Найти цитируемый пост)
Во-вторых, если продолжительность операции не известна, то можно сделать прогрессбар в стиле бегающих квадратиков

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

Автор: artsb 22.9.2012, 18:16
Цитата(Нитонисе @  22.9.2012,  16:05 Найти цитируемый пост)
Никогда такого не делал. Можно элементарный пример?

Копайте в сторону TThread. Добавить в проект поток можно через меню:
1. File -> New -> Other...
2. C++Builder Projects -> C++Builder Files
3. Thread Object

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

Цитата(Нитонисе @  22.9.2012,  16:05 Найти цитируемый пост)
Просто показывать, что программа считает - наверное не очень удобно.

Да. Но бывает, что выхода нет.

Цитата(Нитонисе @  22.9.2012,  16:05 Найти цитируемый пост)
Возможно стоит разбить весь расчет на некоторые структурные части и просто каким либо образом (текстовым или графическим) информировать пользователя в каком блоке сейчас идет вычисление. 

Тоже идея.
Можно ещё разбить всю вашу работу на этапы. Например, если у вас получилось 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).

В основной программе в обработчике клика на кнопку, запускающую расчет, написал такой код.
Код

Thread = new TMyThread(false); // Thread - член класса Form1
Thread->FreeOnTerminate = true;


В результате сам расчет-то выполняется, но основная форма программы все также недоступна во время его выполнения. То есть например отменить выполнение расчета возможности нет. Почему так?

Автор: Dem_max 23.9.2012, 15:36
Да не может быть такого, ты явно что то не то делаешь, но мы не телепаты. Но могу предположить что ты в DoCalculate делаешь длительный расчет, который опять же выполняется в главном потоке окна что и было до этого, вопрос почему конкретно не выполняешь расчет в самом потоке ??

Автор: artsb 23.9.2012, 21:28
Dem_max, правильно говорит. Этим вызовом:
Код

DoCalculate(){Form1->Calculate)

вы запускаете на выполнение расчёты. Причём, запускаете работу с синхронизацией с VCL. Вот всё и виснет. Если в методе Calculate() нет работы с VCL, то можете из потока просто вызвать 
Код

Form1->Calculate

без синхронизации. Тогда всё должно быть нормально. Если же в этом методе есть обращение к VCL, то переносите весь код в поток, НО синхронизировать нужно ТОЛЬКО работу с VCL. Иначе, опять всё повиснет.

Автор: Dem_max 24.9.2012, 02:52
ТС я же тебе ссылку давал там разжован материал для самых тупых.

Автор: Riviero 24.9.2012, 09:38
Код

    Application->ProcessMessages();

Автор: Нитонисе 24.9.2012, 10:35
Цитата(artsb @  23.9.2012,  21:28 Найти цитируемый пост)
Если же в этом методе есть обращение к VCL, то переносите весь код в поток, НО синхронизировать нужно ТОЛЬКО работу с VCL.

Перенести весь код в поток - это ж надо менять области видимости переменных. А что значит "НО синхронизировать нужно ТОЛЬКО работу с VCL"? Вот класс TForm - это класс VCL? Если у меня расчетом непосредственно занимается другой класс TCalculate, являющийся членом класса TForm, значит ли это, что обращение к методам TCalculate я работаю с VCL?

Автор: artsb 24.9.2012, 10:49
Цитата(Нитонисе @  24.9.2012,  10:35 Найти цитируемый пост)
Если у меня расчетом непосредственно занимается другой класс TCalculate, являющийся членом класса TForm, значит ли это, что обращение к методам TCalculate я работаю с VCL? 

Нет, если ваш класс не потомок какого-нибудь VCL-класса.

Цитата(Нитонисе @  24.9.2012,  10:35 Найти цитируемый пост)
А что значит "НО синхронизировать нужно ТОЛЬКО работу с VCL"?

Например,
Код

// расчёты
// расчёты
// расчёты
// расчёты
Form1->Label1->Caption = myResult1;
// расчёты
// расчёты
// расчёты
// расчёты

Вот обращение к свойствам или методам VCL-класса, это и есть то, что я подразумевал под работой с VCL. Т.е. в данном случае, это изменение свойства Caption для Label1. Вот эту операцию и нужно синхронизировать.

Добавлено через 1 минуту и 15 секунд
Цитата(Нитонисе @  24.9.2012,  10:35 Найти цитируемый пост)
Перенести весь код в поток - это ж надо менять области видимости переменных.

Конечно. Придётся как-то приспосабливаться. А, вообще, это нужно было заранее продумывать. Тогда и переделывать ничего бы не пришлось.

Автор: DarkProg 24.9.2012, 11:04
Цитата(Нитонисе @  24.9.2012,  11:35 Найти цитируемый пост)
Если у меня расчетом непосредственно занимается другой класс TCalculate

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

Цитата(Нитонисе @  24.9.2012,  11:35 Найти цитируемый пост)
Перенести весь код в поток - это ж надо менять области видимости переменных.

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

Автор: Нитонисе 24.9.2012, 12:43
Цитата(artsb @  24.9.2012,  10:49 Найти цитируемый пост)
А, вообще, это нужно было заранее продумывать. Тогда и переделывать ничего бы не пришлось.

Вот это точно smile 

Автор: Нитонисе 26.9.2012, 10:51
Переработал код, чтобы все вычисление шло в отдельном потоке, но что-то не работает.
В основной программе есть функция Calculate(), кторая выполняет расчет. Сначала она проверяет исходные данные и наполняет структуру с данными, необходимыми для расчета. Затем запускает отдельный поток, где сам расчет и происходит.
Код

void __fastcall TForm1::Calculate()
{
  //проверка корректности исходных данных, работаем с vcl
  //...

  // заполняем структуру
  TMyStruct S;
  S.a = 1;
  S.b = 2;
  S.c = 3;

  // запускаем отдельный поток
  TMyThread *Thr = new TMyThread(false,&S);

}

Вот так принципиально выглядит класс TMyThread
Код

class TMyThread : public TThread
{
  public:
    __fastcall TMyThread(bool CreateSuspended, TMyStruct *s) : TThread(CreateSuspended) {FreeOnTerminate = true; S = s;}
    void __fastcall Execute();
  private:
    TMyStruct *S;
}
void __fastcall TMyThread::Execute()
{
  int res = S->a + S->b + S->c; 
}
 
Проблема в том, что в поток структура S передается без данных. То есть вычислить res невозможно. Но как так? Ведь я же передал адрес нужной структуры данных из основного потока.

Автор: xvr 26.9.2012, 11:02
Цитата(Нитонисе @  26.9.2012,  10:51 Найти цитируемый пост)
Проблема в том, что в поток структура S передается без данных. То есть вычислить res невозможно. Но как так? Ведь я же передал адрес нужной структуры данных из основного потока. 

Вам нужно скопировать к себе структуру при старте Thread'а, т.к. ваша оригинальная структура выходит из области видимости и разрушается сразу после запуска thread'а

Автор: Нитонисе 26.9.2012, 13:22
Цитата(xvr @  26.9.2012,  11:02 Найти цитируемый пост)
Вам нужно скопировать к себе структуру при старте Thread'а, т.к. ваша оригинальная структура выходит из области видимости и разрушается сразу после запуска thread'а

Так ведь я в потоке работаю не с самой структурой, а с ее адресом. Этот адрес я и передаю при создании потока. Ведь эта операция корректна?

Автор: Dem_max 26.9.2012, 14:38
Ты вышел за пределы Calculate() и все твой объект TMyStruct S разрушен, а поток TMyThread(false,&S); запускается уже после выхода из Calculate() как ты думаешь какой адрес получит поток ???

Автор: xvr 26.9.2012, 16:14
Цитата(Нитонисе @  26.9.2012,  13:22 Найти цитируемый пост)
Ведь эта операция корректна?

Передача адреса корректна, а вот обращение по нему - нет. Dem_max вполне наглядно все расписал  smile 

Автор: Нитонисе 26.9.2012, 16:56
Цитата(Dem_max @  26.9.2012,  14:38 Найти цитируемый пост)
Ты вышел за пределы Calculate() и все твой объект TMyStruct S разрушен, а поток TMyThread(false,&S); запускается уже после выхода из Calculate() как ты думаешь какой адрес получит поток ???

Значит TMyStruct должна быть глобальной переменной? Ну или хотябы членом класса формы.

Автор: DarkProg 26.9.2012, 17:33
Цитата(Нитонисе @  26.9.2012,  17:56 Найти цитируемый пост)
Значит TMyStruct должна быть глобальной переменной? Ну или хотябы членом класса формы. 

Нет, ты в поток должен отдать значения из этой структуры, либо сделать указатель на структуру, выделить под указатель память, и потом передать этот указатель, только выделенную память потом надо будет как-то освободить(либо в деструкторе, либо в конце вычислений).

Автор: Нитонисе 26.9.2012, 17:52
Цитата(DarkProg @  26.9.2012,  17:33 Найти цитируемый пост)
либо сделать указатель на структуру, выделить под указатель память, и потом передать этот указатель

Ну я это и имел ввиду. Объявить глобальную переменную - указатель на структуру, в функции Calculate() выделить память для TMyStruct, заполнить структуру данными, передать указатель классу TMyThread и там использовать эти данные в вычислениях. А освободить память видимо надо в деструкторе TMyThread? Насколько это будет корректно при завершении работы потока штатно (то есть когда вычисления закончены) или внештатно (пользователь нажал кнопку "Отмена", прервав расчет)?

Автор: DarkProg 26.9.2012, 18:58
Цитата(Нитонисе @  26.9.2012,  18:52 Найти цитируемый пост)
Ну я это и имел ввиду. Объявить глобальную переменную - указатель на структуру

Да не нужна вам глобальная переменная! Указатель тем и хорош, что указатель вы отдадите потоку, и поток с ним сможет работать. И ничего ему не будет. В нынешнем варианте у вас объявляется переменная, которая "умирает" как только заканчивается выполнение процедуры, ну и собсно результат известен, а нужен указатель. Т.е. по сути сейчас компилятор выделяет память и освобождает её, и ему невдомёк, что адрес переданный в поток трогать нельзя. Используя указатель типа указатель на структуру вы уже сами начинаете управлять памятью, выделяя её, а переменная-указатель всего лишь является хранилищем для адреса и её разрушение после выхода из процедуры не будет иметь никакого значения для памяти которая выделена под структуру и значений которые там хранятся.

И не путайте глобальную переменную и локальную переменную.

Цитата(Нитонисе @  26.9.2012,  18:52 Найти цитируемый пост)
А освободить память видимо надо в деструкторе TMyThread?

Как реализуете, можно в деструкторе, можно в конце метода Execute, главное не забыть это сделать. Правильнее будет в деструкторе наверное в данном случае, особенно учитывая возможность прерывания вычислений.

Автор: xvr 26.9.2012, 19:01
Сделайте просто локальную копию. Вот так -
Код

class TMyThread : public TThread
{
  public:
    __fastcall TMyThread(bool CreateSuspended, TMyStruct *s) : TThread(CreateSuspended) {FreeOnTerminate = true; S = *s;}
    void __fastcall Execute();
  private:
    TMyStruct S;
}
void __fastcall TMyThread::Execute()
{
  int res = S.a + S.b + S.c; 
}


Автор: artsb 26.9.2012, 20:33
Цитата(xvr @  26.9.2012,  19:01 Найти цитируемый пост)
Сделайте просто локальную копию.

Тогда нужно не забыть перегрузить оператор присваивания. А то всплывёт потом "что-нибудь"...

Автор: xvr 26.9.2012, 21:08
Цитата(artsb @  26.9.2012,  20:33 Найти цитируемый пост)
Тогда нужно не забыть перегрузить оператор присваивания. А то всплывёт потом "что-нибудь"...

Ну если в TMyStruct кроме интов входит еще что-нибудь (например AnsiString), то это "что-нибудь" всплывет сразу  smile Свой оператор присваивания в таком случае явно не будет лишним  smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)