事件处理程序是否在Embarcadero C ++ Builder中重新出现?

时间:2017-01-09 01:13:22

标签: c++ events c++builder reentrancy c++builder-10.1-berlin

我想就如何处理Embarcadero CB10.1问题提出一些建议。在调试配置中编译,并将“禁用所有优化”设置为true。我在Win7上运行。

我有一个简单的测试用例。带有两个按钮的表单。每个按钮的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;
}
//---------------------------------------------------------------------------

- 第一种情况:显示的代码但没有调用ProcessMessages()。

当我运行它并单击按钮1时,CPU使用率几乎会上升 100%约9秒。在此期间,表单变得没有响应。 无法移动表单或单击按钮2。

这正如我所料。

第二种情况:在CPU期间使表单响应用户 密集的功能,我添加了ProcessMessages()调用,如图所示。 现在,我可以移动表单并单击其他按钮。

这并不总是好的,因为我可以再次点击按钮1或 甚至单击按钮2.任一点击都会再次触发CPU密集型功能。为防止CPU密集型功能第二次运行,我制作了一个静态布尔标志“InUse”。我设定了 当函数启动时,当函数完成时清除它。

所以当我进入CPU密集型功能时检查标志 如果它的设置(它必须是通过先前点击一个按钮设置的),我 显示一条消息,然后等待标志清除。

但是标志永远不会清除,我的程序在'while'语句中循环 永远。我希望程序只是等待CPU密集型功能 完成然后再次运行它。

如果我在遇到死锁后在Spin()函数中设置断点, 它永远不会触发,表明这两个事件都没有执行。

我知道VCL不是线程安全的,但在这里,所有处理都需要 放在主线程中。在我的实际代码中,有很多调用 VCL功能因此CPU密集型功能必须保留在主要功能中 线程。

我考虑过关键部分和互斥体,但因为一切都在 主线程,任何使用它们都没有阻塞。

也许它是一个堆栈问题?有没有一种解决方案可以让我在没有死锁的情况下处理这个问题?

2 个答案:

答案 0 :(得分:1)

  

第二种情况:为了在CPU密集型功能期间使表单响应用户,我添加了ProcessMessages()调用,如图所示。现在,我可以移动表单并单击其他按钮。

这总是错误的解决方案。处理这种情况的正确方法是将CPU密集型代码移动到单独的工作线程,然后让按钮事件启动该线程的新实例(如果它尚未运行)。或者,保持线程在一个循环中运行,该循环在没有工作要做时休眠,然后让每个按钮事件通知线程唤醒并完成其工作。无论哪种方式,从不阻止主UI线程!

  

这并不总是好的,因为我可以再次点击按钮1,甚至可以点击按钮2.任何一次点击都会再次触发CPU密集型功能。

     

为防止CPU密集型功能第二次运行,我制作了一个静态布尔标志“InUse”。我在函数启动时设置它,并在函数完成时清除它。

更好的方法是在执行工作时禁用按钮,并在完成后重新启用它们。然后,工作无法重新开始。

但是,即使你保留了你的旗帜,如果已经设置了旗帜,你的函数也应该退出而不做任何事情。

无论哪种方式,您都应该显示一个UI,告诉用户何时正在进行工作。如果工作是在一个单独的线程中完成的,那么这将变得更容易管理。

  

所以我在输入CPU密集型函数时检查标志,如果它的设置(必须通过先前点击按钮设置),我会显示一条消息,然后等待标志清除。

     

但是旗帜永远不会清除并且

这是因为你只是运行一个无效循环,所以它不允许代码继续进行。当然不能完成现有工作并重置旗帜。

您可以对现有代码进行的最小修复,无需重新编写即可将CPUIntensive()更改为使用return 0代替while (InUse) {} InUse 1}}是真的。这将允许调用ProcessMessages()退出并将控制权返回到等待完成运行的上一个CPUIntensive()调用。

  

我知道VCL不是线程安全的,但是在这里,所有处理都在主线程中进行。

Thay是一个大错误。

  

在我的实际代码中,有很多对VCL函数的调用,因此CPU密集型函数必须保留在主线程中。

这不是在主线程中执行工作的充分理由。将其移动到它所属的工作线程,并在需要访问UI时使其与主线程同步。在工作线程中尽可能多地工作,并在绝对必要时进行同步。

答案 1 :(得分:0)

我的问题不是关于线程,而是如何防止多次点击按钮同时被操作,而不是表单没有响应。所有这些都在我的单线程VCL程序中。正如我所看到的,当我没有调用ProcessMessages()时,一旦单击一个按钮(直到事件处理程序完成其处理),表单就会没有响应。当我添加对ProcessMessages()的调用时,表单是TOO响应的,因为鼠标点击导致事件处理程序运行即使,同样的鼠标单击事件处理程序只是部分完成时称为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线程消息泵并且等待鼠标按钮事件被触发。