一个最简单的多线程例子,使用了TEvent对象,运行结果有点不理想,可能是什么原因呢?

ooolinux 2018-05-12 11:37:03
一个最简单的多线程例子,使用了TEvent对象,运行结果有点不理想,可能是什么原因呢?
源代码如下:
Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>

#include <syncobjs.hpp>
#include "Unit2.h"
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TEdit *Edit1;
TEdit *Edit2;
TEdit *Edit3;
TButton *Button1;
void __fastcall Button1Click(TObject *Sender);
private: // User declarations
TMyThread *th[3];
public: // User declarations
__fastcall TForm1(TComponent* Owner);
__fastcall ~TForm1();
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
extern TEvent *event[3];
//---------------------------------------------------------------------------
#endif


Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include "Unit2.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
TEvent *event[3];
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
th[0]=new TMyThread(false,0,Edit1);
th[1]=new TMyThread(false,1,Edit2);
th[2]=new TMyThread(false,2,Edit3);

for(int i=0;i<3;i++)
event[i]=new TEvent(NULL,true,false,NULL);
}
//---------------------------------------------------------------------------
__fastcall TForm1::~TForm1()
{
for(int i=0;i<3;i++)
th[i]->Terminate();
Sleep(100);
for(int i=0;i<3;i++)
delete event[i];
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
for(int i=0;i<3;i++)
event[i]->SetEvent();
}
//---------------------------------------------------------------------------


Unit2.h
//---------------------------------------------------------------------------

#ifndef Unit2H
#define Unit2H
//---------------------------------------------------------------------------
#include <Classes.hpp>
//---------------------------------------------------------------------------
class TMyThread : public TThread
{
private:
int id;
TEdit *edt;
AnsiString str;
protected:
void __fastcall Execute();
void __fastcall updateText();
public:
__fastcall TMyThread(bool CreateSuspended,int id,TEdit *edt);
};
//---------------------------------------------------------------------------
#endif


Unit2.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit2.h"
#include "Unit1.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------

// Important: Methods and properties of objects in VCL can only be
// used in a method called using Synchronize, for example:
//
// Synchronize(UpdateCaption);
//
// where UpdateCaption could look like:
//
// void __fastcall TMyThread::UpdateCaption()
// {
// Form1->Caption = "Updated in a thread";
// }
//---------------------------------------------------------------------------

__fastcall TMyThread::TMyThread(bool CreateSuspended,int id,TEdit *edt)
: TThread(CreateSuspended)
{
this->id=id;
this->edt=edt;
FreeOnTerminate=true;
}
//---------------------------------------------------------------------------
void __fastcall TMyThread::Execute()
{
//---- Place thread code here ----
int i=0;
while(!Terminated)
{
if(event[id]->WaitFor(INFINITE)==wrSignaled)
{
event[id]->ResetEvent();
str=IntToStr(i++);
Synchronize(updateText);
}
}
}
//---------------------------------------------------------------------------
void __fastcall TMyThread::updateText()
{
edt->Text=str;
}
//---------------------------------------------------------------------------

...全文
1380 53 打赏 收藏 转发到动态 举报
写回复
用AI写文章
53 条回复
切换为时间正序
请发表友善的回复…
发表回复
华山沦贱 2018-05-17
  • 打赏
  • 举报
回复
刚搜了下,楼主在去年就在论坛提出过同一个问题,半年了总算真正解决了
titan_ysl 2018-05-17
  • 打赏
  • 举报
回复
@ooolinux 我的理解,TEvent构造函数的Name参数主要是为了在其它线程或者进程中用同一个Name来创建event对象,得到同一个Handle内核对象。 应该是这样理解的,但我不知道为什么实际结果不大相符。
titan_ysl 2018-05-17
  • 打赏
  • 举报
回复
只有在一个线程或进程访问多个TEvent对象时,Name才能设为NULL.
titan_ysl 2018-05-17
  • 打赏
  • 举报
回复
@ooolinux 如果创建TEvent时Name都为空,第二个TEvent的Handle也是新建的,不等于第一个,按理说你的写法没问题,但是,在gongzhu110 的提醍下,我发现一个问题,就是这样写代码时, for(int i=0;i<3;i++) event[i]=new TEvent(NULL,false,true,NULL); 就是手工触发为真,初始值为有信号时,在第一次收信号号时,只要event[0]->WaitFor(INFINITE)等到了wrSignaled信号,event[1]和event[2]的信号也复位了,但是以后再给信号却又能正确触发了。但是直接用api函数 for(int i=0; i<3; i++) g_hEvent[i]=CreateEvent(NULL, FALSE, TRUE, NULL); 却没这个问题,于是我查了TEvent在Create时的帮助, Set Name to provide a name for a new event object or to specify an existing named event object. If no other thread or process will need to access the event object to wait for its signal, Name can be left blank. 它提到了要给一个名字来标识新的事件对象,我就加了名字来测试,结果发现能正确分开初始信号了,所以我的结论就是:TEvent类,如有多个事件对象,Name不应为空。否则会出错,但是api函数,CreateEvent则没这个问题。
ooolinux 2018-05-17
  • 打赏
  • 举报
回复
我的理解,TEvent构造函数的Name参数主要是为了在其它线程或者进程中用同一个Name来创建event对象,得到同一个Handle内核对象。
ooolinux 2018-05-17
  • 打赏
  • 举报
回复
@titan_ysl 如果创建TEvent时Name都为空,第二个TEvent的Handle会等于第一个吗?如果是,那等于三个event对象其实是同一个。但从代码运行结果看不是这样。
ooolinux 2018-05-17
  • 打赏
  • 举报
回复
@titan_ysl @gongzhu110 我的理解,TEvent构造函数的Name参数主要是为了防止和系统存在的其它内核对象冲突吧, Help里并没有说“多个事件对象,Name不应为空”。
ooolinux 2018-05-17
  • 打赏
  • 举报
回复
@gongzhu110 new TEvent(NULL,false,true,NULL); 你的代码第3个参数( bool InitialState)为true,表示一创建初始就是有信号的,第一次不用SetEvent就会触发。
华山沦贱 2018-05-17
  • 打赏
  • 举报
回复
titan_ysl 算是彻底解决了! 我查bcb只有这么个解释,没有像titan_ysl 那么深入,一个参数困扰了这么久: Description Use TEvent to signal that an event has occurred or a state has been reached. In a multi-threaded application, use TEvent to allow one thread to signal to other threads that an event has occurred. The handle of a TEvent object can also be used to communicate with other processes, so that an application can coordinate the timing of events with other applications. For example, use the handle of a TEvent object to wait until another process is ready to transfer information. In a single-threaded application, use TEvent to coordinate between sections of code that respond to asynchronous events such as system events.
titan_ysl 2018-05-17
  • 打赏
  • 举报
回复
或者 event[i]=new TEvent(NULL,true,false,AnsiString("BiaoShi")+i); 现在都不应出问题了。 当然了,在这样写时, event[i]=new TEvent(NULL,false,true,AnsiString("BiaoShi")+i); 也要event[id]->ResetEvent();去掉
titan_ysl 2018-05-17
  • 打赏
  • 举报
回复
__fastcall TEvent(Windows::PSecurityAttributes EventAttributes, bool ManualReset, bool InitialState, const AnsiString Name); 多个事件对象,Name不应为空,改为下面这样试试: event[i]=new TEvent(NULL,false,true,AnsiString("BiaoShi")+i); Set Name to provide a name for a new event object or to specify an existing named event object. If no other thread or process will need to access the event object to wait for its signal, Name can be left blank. Name can be a string of up to 260 characters, not including the backslash character (\). If Name is used to specify an existing event object, the value must match the name of the existing event in a case-sensitive comparison. If Name matches the name of an existing semaphore, mutex, or file-mapping object, the TEvent object will be created with Handle set to 0 and all method calls will fail.
ooolinux 2018-05-17
  • 打赏
  • 举报
回复
引用 35 楼 gongzhu110 的回复:
[quote=引用 33 楼 u010165006 的回复:] @gongzhu110 ok,用Timer触发有区别吗,用按钮控制timer的启停。
理论上没有区别,但是我习惯用线程调用线程,而且可以防止界面卡顿。第二个问题,Sleep()没有效果,0-10都试了。 系统已启动线程0就走了一遍了。偶尔会是线程1。这个跟按钮线程调用没有关系。 测试发现的一个小现象:如果界面在“跑数”的过程中,拖动界面的话,最终不会跑到99或100,会小几个数[/quote] 多线程还是有点复杂。
ooolinux 2018-05-17
  • 打赏
  • 举报
回复
@早打大打打核战争 帮我看看这个~ https://bbs.csdn.net/topics/392378911
ooolinux 2018-05-17
  • 打赏
  • 举报
回复
引用 51 楼 DelphiGuy 的回复:
TEvent的源码在windows上就是调用CreateEvent,没有特殊处理: constructor TEvent.Create(EventAttributes: PSecurityAttributes; ManualReset, InitialState: Boolean; const Name: string; UseCOMWait: Boolean); {$IFDEF MSWINDOWS} begin inherited Create(UseCOMWait); FHandle := CreateEvent(EventAttributes, ManualReset, InitialState, PChar(Name)); end; 。。。
inherited Create(UseCOMWait); 这句怎么理解?
ooolinux 2018-05-17
  • 打赏
  • 举报
回复
引用 45 楼 titan_ysl 的回复:
@ooolinux 如果创建TEvent时Name都为空,第二个TEvent的Handle也是新建的,不等于第一个,按理说你的写法没问题,但是,在gongzhu110 的提醍下,我发现一个问题,就是这样写代码时, for(int i=0;i<3;i++) event[i]=new TEvent(NULL,false,true,NULL); 就是手工触发为真,初始值为有信号时,在第一次收信号号时,只要event[0]->WaitFor(INFINITE)等到了wrSignaled信号,event[1]和event[2]的信号也复位了,但是以后再给信号却又能正确触发了。但是直接用api函数 for(int i=0; i<3; i++) g_hEvent[i]=CreateEvent(NULL, FALSE, TRUE, NULL); 却没这个问题,于是我查了TEvent在Create时的帮助, Set Name to provide a name for a new event object or to specify an existing named event object. If no other thread or process will need to access the event object to wait for its signal, Name can be left blank. 它提到了要给一个名字来标识新的事件对象,我就加了名字来测试,结果发现能正确分开初始信号了,所以我的结论就是:TEvent类,如有多个事件对象,Name不应为空。否则会出错,但是api函数,CreateEvent则没这个问题。
看来创建多个TEvent对象最好还是应该有Name了。
  • 打赏
  • 举报
回复
TEvent的源码在windows上就是调用CreateEvent,没有特殊处理: constructor TEvent.Create(EventAttributes: PSecurityAttributes; ManualReset, InitialState: Boolean; const Name: string; UseCOMWait: Boolean); {$IFDEF MSWINDOWS} begin inherited Create(UseCOMWait); FHandle := CreateEvent(EventAttributes, ManualReset, InitialState, PChar(Name)); end; 。。。
ooolinux 2018-05-17
  • 打赏
  • 举报
回复
引用 48 楼 gongzhu110 的回复:
刚搜了下,楼主在去年就在论坛提出过同一个问题,半年了总算真正解决了
后来忙了下学习C#就忘了,想起来了重新发出来问问,看来有问题要及时解决,还要感谢三位~
华山沦贱 2018-05-16
  • 打赏
  • 举报
回复
引用 16 楼 titan_ysl 的回复:
奇怪,我编译了你的工程,加了Application->ProcessMessages();会出问题,不加反而正确,我的测试平台是win10,64位,c++builder6, 我连续按动了300次,三个值都是一样的。下面是我编译好的文件,你试下。 https://pan.baidu.com/s/10GNTJyxrv03IcDf9kcYJtw
我把按键改为多线程玩了下,发现你的代码还是一样的有bug:

DWORD WINAPI _TestProc(PVOID pvParam)
{
     TEvent **evt = (TEvent **)pvParam;
     
      for(int i=0;i<3;i++)
      {
         evt[i]->SetEvent();
      }

     return 0;
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
      HANDLE hThread = CreateThread(NULL, 0, _TestProc,PVOID(event), 0, NULL);
      CloseHandle(hThread);
}
华山沦贱 2018-05-16
  • 打赏
  • 举报
回复
引用 33 楼 u010165006 的回复:
@gongzhu110 ok,用Timer触发有区别吗,用按钮控制timer的启停。
理论上没有区别,但是我习惯用线程调用线程,而且可以防止界面卡顿。第二个问题,Sleep()没有效果,0-10都试了。 系统已启动线程0就走了一遍了。偶尔会是线程1。这个跟按钮线程调用没有关系。 测试发现的一个小现象:如果界面在“跑数”的过程中,拖动界面的话,最终不会跑到99或100,会小几个数
ooolinux 2018-05-16
  • 打赏
  • 举报
回复
@gongzhu110 ok,用Timer触发有区别吗,用按钮控制timer的启停。
加载更多回复(31)

13,824

社区成员

发帖
与我相关
我的任务
社区描述
C++ Builder相关内容讨论区
社区管理员
  • 基础类社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧