2017-01-09 4 views
3

Re-entrancy로 Embarcadero CB10.1 문제를 처리하는 방법에 대한 조언을하고 싶습니다. "모든 최적화 비활성화"가 true로 설정된 디버그 구성으로 컴파일되었습니다. 나는 Win7에서 실행 중이다.Embarcadero C++ Builder에서 이벤트 핸들러가 재진입합니까?

간단한 테스트 케이스가 있습니다. 두 개의 단추가있는 폼. 각 단추의 OnClick 이벤트 처리기는 동일한 CPU 집중 기능을 호출합니다. 아래는 프로그램 파일이 뒤 따르는 헤더 파일입니다.

#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을 클릭하면 CPU 사용량이 약 012 초에 최대 100 %로 점프합니다. 이 시간 동안 양식이 응답하지 않게됩니다. 양식을 이동하거나 버튼 2를 클릭 할 수 없습니다.

예상대로 작동합니다.

두 번째 경우 : CPU 집중 기능 중 사용자가 응답하는 양식을 만들기 위해 표시된대로 ProcessMessages() 호출을 추가했습니다. 이제 양식을 이동하고 다른 버튼을 클릭 할 수 있습니다.

버튼 1을 다시 클릭 할 수도 있고 버튼 2를 클릭하기 만해도 항상 좋은 것은 아닙니다. 클릭하면 CPU 집중 기능이 다시 실행됩니다. CPU 집약 함수가 두 번째로 실행되는 것을 방지하기 위해 정적 부울 플래그 "InUse"를 만들었습니다. 함수가 시작될 때 으로 설정하고 함수가 완료되면이를 지 웁니다.

그래서 CPU 집약 기능을 입력하면 버튼을 이전 클릭으로 설정해야합니다 ( ) 메시지를 표시하고 플래그가 지워질 때까지 기다리는 경우 플래그를 확인합니다.

하지만 플래그가 지워지지 않고 내 프로그램이 'while'문에서 반복적으로 실행됩니다. 나는이 프로그램이 CPU 집약 기능 이 완료 될 때까지 기다렸다가 다시 실행하기를 바란다.

교착 상태에 도달 한 후 Spin() 함수에서 중단 점을 설정하면 이벤트가 실행되지 않음을 나타내는 절대 실행되지 않습니다.

나는 VCL이 스레드로부터 안전하지는 않지만 여기서 모든 처리는 메인 스레드에서 을 차지한다는 것을 알고있다. 내 실제 코드에는 VCL 함수에 대한 호출이 많으므로 CPU 집약 함수가 기본 스레드 에 남아 있어야합니다.

중요한 부분과 뮤텍스를 고려했지만 모든 것이 메인 스레드에 있으므로 아무 것도 차단하지 않습니다.

아마도 스택 문제일까요? 교착 상태없이이 문제를 처리 할 수있는 솔루션이 있습니까?

+1

이것은 단일 스레드 프로그램이므로'while (flag) {}; '라고 말하면 물론 영원히 반복됩니다. 다른 코드는 실행되고 있지 않으므로'flag '의 값이 어떻게 바뀔 수 있습니까? 중첩 호출에서 돌아와 외부 호출을 완료 할 수 있어야합니다. –

+0

사용자가 버튼을 클릭 한 횟수만큼 작업을 실행하려면 카운터가 있어야합니다. 그러나 작업이 완료 될 때까지 두 버튼을 모두 비활성화하는 것이 더 현명 할 수 있습니다. –

+1

[Delphi에서 Application.ProcessMessages가 수행하는 작업을 이해할 수 없음] 가능한 복제본 (http://stackoverflow.com/questions/25181713/i-do-not-understand-what-application-processmessages-in-delphis -doing) –

답변

1

두 번째 경우 : CPU 집중 기능을 수행하는 동안 양식을 사용자가 응답하도록하기 위해 표시된대로 ProcessMessages() 호출을 추가했습니다. 이제 양식을 이동하고 다른 버튼을 클릭 할 수 있습니다.

항상 잘못된 해결책입니다.이 상황을 처리하는 방법은 CPU 집중 코드를 별도의 작업자 스레드로 이동 한 다음 단추 이벤트가 아직 실행되지 않은 경우 해당 스레드의 새 인스턴스를 시작하게하는 것입니다. 또는 스레드가 수행 할 작업이 없을 때 잠자기 루프를 계속 실행 한 다음 각 단추 이벤트가 스레드에게 깨어나서 작업을 알리는 신호를 보내도록하십시오. 어느 쪽이든, NEVER 메인 UI 스레드를 차단하십시오!

버튼 1을 다시 클릭하거나 버튼 2를 클릭 할 수 있기 때문에 항상 좋지 않습니다. 클릭하면 CPU 집중 기능이 다시 실행됩니다.

CPU 집중 기능이 두 번째로 실행되는 것을 방지하기 위해 정적 부울 플래그 "InUse"를 만들었습니다. 함수가 시작될 때이를 설정하고 함수가 완료되면이를 지 웁니다.

더 좋은 방법은 작업이 수행되는 동안 버튼을 비활성화하고 완료되면 다시 활성화하는 것입니다. 그런 다음 작업을 다시 시작할 수 없습니다.

플래그를 유지하더라도 플래그가 이미 설정되어 있으면 함수는 아무 것도하지 않고 종료해야합니다.

어느 쪽이든, 작업이 진행 중일 때 사용자에게 알려주는 UI를 표시해야합니다. 작업이 별도의 스레드에서 수행되면 관리가 더 쉬워집니다.

그래서 CPU 집약 기능에 들어가면 플래그를 확인하고 버튼이 이전 클릭으로 설정되어 있어야합니다. 플래그를 지우고 플래그를 지울 때까지 기다립니다.

그러나 플래그는 클리어 결코 그냥 아무것도하지 않는 무한 루프를 실행하는, 그래서 코드가 더 이상 진행하는 것을 허용하지 않기 때문이다

. 그리고 확실하게 기존 작업을 끝내지 않고 플래그를 다시 설정하십시오.

작은 수정 당신은 InUse에 해당하는 경우 while (InUse) {} 대신 return 0을 사용하는 CPUIntensive()을 변경하는 것입니다 다시 작성하지 않고 기존의 코드를 만들 수 있습니다. 그러면 ProcessMessages()에 대한 호출이 종료되고 실행을 끝내기 위해 대기중인 이전 CPUIntensive() 호출로 제어를 되돌릴 수 있습니다.

VCL은 스레드로부터 안전하지 않지만 여기서는 모든 처리가 주 스레드에서 발생합니다.

Thay는 큰 실수입니다.

내 실제 코드에는 CPU 집약 기능이 메인 스레드에 남아 있어야하므로 VCL 함수가 많이 호출됩니다.

주 스레드에서 작업을 수행할만한 충분한 이유가 아닙니다. 그것을 UI에 액세스해야 할 때마다 주 스레드와 동기화되도록 작업자 스레드로 이동하십시오. 가능하면 작업자 스레드에서 많은 작업을 수행하고 절대적으로 필요한 경우에만 동기화하십시오.

0

제 질문은 스레드에 관한 것이 아니라 폼을 응답하지 않게하면서 버튼을 여러 번 클릭하는 것을 방지하는 방법이었습니다. 이 모든 것이 내 단일 스레드 VCL 프로그램에 포함되어 있습니다. ProcessMessages()에 대한 호출이 없을 때, 단추가 클릭되면 (이벤트 처리기가 처리를 완료 할 때까지) 양식이 응답하지 않게되었습니다. ProcessMessages()에 대한 호출을 추가하면 마우스 클릭으로 인해 발생하는 이벤트 핸들러가 너무 커서 응답하지 못했습니다. 동일한 마우스 클릭 이벤트 처리기는 ProcessMessages()를 호출했을 때 부분적으로 만 완료되었습니다. 이벤트 처리기는 재진입이 아니지만 두 번째 마우스 버튼을 누르면 Windows/VCL이 다시 입력합니다.

마우스 단추 이벤트 처리를 지연하는 동시에 메시지를 처리하는 동안 양식이 응답하지 않는 것처럼 보일 수있는 방법이 필요했습니다.

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; 

지금 이벤트 핸들러를 다시 입력하지 않고 처리를 완료 가져옵니다 여기

내가 ProcessMessages에 전화를 교체와 함께 종료 코드입니다. 모든 비 마우스 버튼 이벤트가 처리됩니다. 이벤트 처리기가 완료되면 제어가 기본 VCL 스레드 메시지 펌프로 돌아가고 대기중인 마우스 버튼 이벤트가 시작됩니다.