Я хотел бы спросить совета о том, как справиться с проблемой Embarcadero CB10.1 с повторным входом. Скомпилировано в конфигурации отладки, для параметра «Отключить все оптимизации» задано значение true. Я работаю на Win7.
У меня есть простой контрольный пример. Форма с двумя кнопками. Обработчик события OnClick для каждой кнопки вызывает одну и ту же функцию с интенсивным использованием ЦП. Ниже приведен заголовочный файл, за которым следует файл программы.
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TButton *Button1;
TButton *Button2;
void __fastcall Button1Click(TObject *Sender);
void __fastcall Button2Click(TObject *Sender);
private: // User declarations
double __fastcall CPUIntensive(double ButonNo);
double __fastcall Spin(double Limit);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Button1->Caption = "Pushed";
double retv = CPUIntensive(1);
Button1->Caption = "Button1";
if (retv) ShowMessage("Button1 Done");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
Button2->Caption = "Pushed";
double retv = CPUIntensive(2);
Button2->Caption = "Button2";
if (retv) ShowMessage("Button2 Done");
}
//---------------------------------------------------------------------------
double __fastcall TForm1::CPUIntensive(double ButtonNo)
{
//
static bool InUse = false;
if (InUse) {
ShowMessage("Reentered by button number " + String(ButtonNo));
while (InUse) {};
}
double retv;
InUse = true;
retv = Spin(30000); // about 9 seconds on my computer
//retv += Spin(30000); // uncomment if you have a faster computer
//retv += Spin(30000);
InUse = false;
return retv;
}
//---------------------------------------------------------------------------
double __fastcall TForm1::Spin(double Limit)
{
double k;
for (double i = 0 ; i < Limit ; i++) {
for (double j = 0 ; j < Limit ; j++) {
k = i + j;
// here there can be calls to other VCL functions
Application->ProcessMessages(); // added so UI would be responsive (2nd case)
}
}
return k;
}
//---------------------------------------------------------------------------
—
1-й случай: код показан, но без вызова ProcessMessages ().
Когда я запускаю это и нажимаю кнопку 1, загрузка процессора увеличивается почти до
100% в течение примерно 9 секунд. Форма перестает отвечать на запросы в течение этого времени.
Не могу переместить форму или нажать на кнопку 2.
Это работает, как я ожидал.
2-й случай: чтобы форма реагировала на пользователя во время работы процессора
интенсивная функция, я добавил вызов ProcessMessages (), как показано.
Теперь я могу перемещать форму и нажимать на другие кнопки.
Это не всегда хорошо, потому что я могу снова нажать кнопку 1 или
даже нажмите на кнопку 2. Любой щелчок приведет к повторному запуску функции интенсивного использования процессора. Чтобы не запускать функцию, интенсивно использующую процессор, во второй раз, я установил статический логический флаг «InUse». Я установил это
когда функция запускается и очищает ее, когда функция завершается.
Поэтому я проверяю флаг, когда я вхожу в интенсивную работу процессора и
если его установить (он должен был быть установлен предыдущим нажатием кнопки), я
показать сообщение, а затем подождите, пока флаг не очистится.
Но флаг никогда не сбрасывается, и моя программа зацикливается на операторе while
навсегда. Я хотел бы, чтобы программа просто ждала интенсивной работы процессора
чтобы завершить, а затем просто запустите его снова.
Если я установил точку останова в функции Spin () после того, как попал в тупик,
он никогда не срабатывает, указывая, что ни одно из событий не выполняется.
Я знаю, что VCL не является потокобезопасным, но здесь вся обработка занимает
место в основной теме. В моем фактическом коде есть много вызовов
Функции VCL, поэтому интенсивная загрузка ЦП должна оставаться в главном
нить.
Я рассмотрел критические разделы и мьютексы, но так как все в
основной поток, любое их использование не блокирует.
Может быть, проблема в стеке? Есть ли решение, которое позволяет мне справиться с этим без тупика?
2-й случай: чтобы форма реагировала на пользователя во время интенсивной загрузки процессора, я добавил вызов ProcessMessages (), как показано. Теперь я могу перемещать форму и нажимать на другие кнопки.
Это всегда неправильное решение. правильный Чтобы справиться с этой ситуацией, нужно переместить код, интенсивно использующий процессор, в отдельный рабочий поток, а затем заставить ваши события кнопок запускать новый экземпляр этого потока, если он еще не запущен. Или оставьте поток работающим в цикле, который спит, когда у него нет работы, а затем пусть каждое событие кнопки сигнализирует о том, что поток просыпается и выполняет свою работу. В любом случае, НИКОГДА заблокировать основной поток пользовательского интерфейса!
Это не всегда хорошо, потому что я могу снова нажать кнопку 1 или даже нажать кнопку 2. Любой щелчок снова отключит функцию, интенсивно использующую процессор.
Чтобы не запускать функцию, интенсивно использующую процессор, во второй раз, я установил статический логический флаг «InUse». Я устанавливаю это, когда функция запускается, и очищаю ее, когда функция завершается.
Лучшим способом было бы отключить кнопки во время выполнения работы и повторно включить их после завершения. Тогда работа не может быть повторно введена для начала.
Но даже если вы сохраняете свой флаг, ваша функция должна просто выйти, ничего не делая, если флаг уже установлен.
В любом случае, вы должны отобразить пользовательский интерфейс, сообщающий пользователю, когда работа выполняется. Это становится проще в управлении, если работа выполняется в отдельном потоке.
Поэтому я проверяю флаг, когда я вхожу в функцию с интенсивным использованием ЦП и, если она установлена (она должна была быть установлена предыдущим нажатием кнопки), я показываю сообщение и затем жду, пока флаг не очистится.
Но флаг никогда не очищается и
Это потому, что вы просто запускаете бесконечный цикл, который ничего не делает, поэтому он не позволяет коду развиваться дальше. И уж точно не закончить существующую работу и сбросить флаг.
наименьшее исправление, которое вы можете внести в существующий код, не переписывая его, чтобы изменить CPUIntensive()
использовать return 0
вместо while (InUse) {}
когда InUse
правда. Это позволит позвонить ProcessMessages()
выйти и вернуть управление обратно к предыдущему CPUIntensive()
вызов, который ждет, чтобы закончить работу.
Я знаю, что VCL не является потокобезопасным, но здесь вся обработка происходит в основном потоке.
Это большая ошибка.
В моем реальном коде есть много обращений к функциям VCL, поэтому интенсивная загрузка ЦП должна оставаться в основном потоке.
Это недостаточно веская причина для выполнения работы в главном потоке. Переместите его в рабочий поток, где он принадлежит, и синхронизируйте его с основным потоком всякий раз, когда ему необходим доступ к пользовательскому интерфейсу. Делайте как можно больше работы в рабочем потоке и синхронизируйте только тогда, когда это абсолютно необходимо.
Мой вопрос был не о потоках, а о том, как предотвратить одновременное нажатие кнопок, при этом форма не перестала отвечать. Все это в моей однопоточной программе VCL. Как я увидел, когда у меня не было вызова ProcessMessages (), форма перестала отвечать после нажатия кнопки (до тех пор, пока обработчик события не завершил свою обработку). Когда я добавил вызов ProcessMessages (), форма была слишком СКОРОСТНОЙ, потому что щелчки мыши вызывали запуск обработчиков событий ДАЖЕ ЕСЛИ обработчик события того же щелчка мыши был завершен только частично, когда вызвал ProcessMessages (). Обработчики событий не повторяются, но Windows / VCL повторно вводил их при нажатии второй кнопки мыши.
Мне нужен был способ отложить обработку событий кнопки мыши, в то же время обрабатывая сообщения, чтобы форма не отображалась без ответа.
ProcessMessages () не собирался работать здесь. Он отправлял каждое сообщение, найденное в очереди сообщений.
Я нашел способ пойти частично, версия ProcessMessages, которая проверила очередь сообщений и, если там было сообщение, не являющееся кнопкой мыши, отправила его. В противном случае оставьте сообщение в очереди на потом.
Вот код, который я использовал для замены вызова ProcessMessages:
// set dwDelay to handle the case where no messages show up
MSG msg;
DWORD dwWait = MsgWaitForMultipleObjects(0, NULL, FALSE, dwDelay, QS_ALLINPUT);
if (dwWait == WAIT_TIMEOUT) { // Timed out?
// put code here to handle Timeout
return;
}
// Pump the message queue for all messages except Mouse button messages
// from 513 to 521 (0x0201 to 0x0209)
bool MsgAvailable;
while (true) {
MsgAvailable = PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
if (!MsgAvailable) break; // no messages available
if (msg.message <= WM_MOUSEMOVE) {
// Message from WM_NULL to and including WM_MOUSEMOVE
GetMessage(&msg, NULL, WM_NULL, WM_MOUSEMOVE);
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
}
if (msg.message >= (WM_MOUSELAST+1)) {
// Message from WM_MOUSELAST+1 to the last message possible
GetMessage(&msg, NULL, WM_MOUSELAST+1, 0xFFFFFFFF);
TranslateMessage(&msg);
DispatchMessage(&msg);
continue;
}
// if all that's left is mouse button messages, get out
if (msg.message > WM_MOUSEMOVE || msg.message < WM_MOUSELAST+1) break;
}
return;
Теперь обработчик событий завершает свою обработку без повторного ввода. Все не кнопки мыши события обрабатываются. Когда обработчик событий завершен, управление возвращается к главному насосу сообщений потока VCL, и ожидают события (и) ожидающей кнопки мыши.