【转】MFC 之CEvent

event是用来同步不同线程的。一旦一个线程结束了自己对全局资源的使用,他通过调用SetEvent通知别人可以使用了。如果这个被删了,其他线程将被阻塞

当一个线程里调用了::WaitForSingleObject(pEvent->m_hObject, INFINITE);时,会在这句上等待,而不往下操作,这时你就要用SetEvent将这等待改变,让线程能继续往下执行;
总的来说,这是用于线程同步的

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

CEvent类祥解
CEvent 类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。事件告诉线程何时去执行某一给定的任务,从而使多个线程流平滑。例如在某些网络应用程序中,一个线程(记为A)负责监听通信端口,另一个线程(记为B)负责更新用户数据。通过使用CEvent类,线程A可以通知线程B何时更新用户数据,这样线程B可以尽快地更新用户数据。每一个CEvent对象可以有两种状态:有信号状态(signaled)和无信号状态(nonsignaled)。线程监视位于其中的CEvent类对象的状态,并在相应的时候采取相应的操作。
     在MFC中,CEvent类对象有两种类型,分别是所谓的人工事件和自动事件。对于自动事件,当其获得信号后,就会释放下一个可用的线程。一个自动 CEvent对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放所有可利用线程,直到调用成员函数ReSetEvent ()将其设置为无信号状态时为止。注意,在创建CEvent类的对象时,默认创建的是自动事件。
CEvent的各成员函数的原型与参数说明如下。 
CEvent(BOOL bInitiallyOwn = FALSE,
BOOL bManualReset = FALSE,
LPCTSTR lpszName = NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
 
bInitiallyOwn:若bInitiallyOwn为TRUE,则使CMultilock类对象和CSingleLock类对象的线程可用;否则,要访问资源的线程必须等待。该参数的默认值为FALSE。
bManualReset:指定要创建的CEvent对象是属于手工事件还是自动事件。为TRUE,则为手工事件,否则为自动事件。该参数默认值为FALSE。
lpszName:指定要创建的事件对象的名,如果该事件对象将跨进程使用,则此参数不能为NULL。如果该参数和一个已经存在的CEvent对象相同,则该构造函数返回一个对这个已存在对象的引用;如果参数和一个已存在的非CEvent类的同步对象(如CMutex)相同,则对象创建失败;
lpsaAttribute:指向SECURITY_ATTRIBUTES结构的指针,该参数决定要创建的事件对象的安全属性,一般置为NULL。
在事件对象建成后,可以调用其成员函数来改变其状态。 
BOOL CEvent::SetEvent ();
 
将CEvent类对象的状态设置为有信号状态,并且释放所有等待的线程;如果该事件是人工事件,则CEvent类对象保持为有信号状态,直到调用成员函数ResetEvent()将其重新设为无信号状态时为止,这样该事件就可以释放多个线程;如果CEvent类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent类对象由系统自动重置为无信号状态,除非一个线程被释放。
如果该函数执行成功,则返回非零值,否则返回零。 
BOOL CEvent::ResetEvent();
 
该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。
如果该函数执行成功,返回非零值,否则返回非零。 
BOOL CEvent::PulseEvent()
 
发送一个事件脉冲,该函数完成一系列操作后才返回。对于自动事件,PulseEvent()将事件设置为有信号状态,等待一个线程被释放,将事件重置为无信号状态,然后PulseEvent()返回;对于人工事件,则将等待该事件的所有线程被释放,事件被自动重置为无信号状态,然后PulseEvent()返回。
一个CEvent对象在线程中被创建后,自动处于无信号状态,但在另一个线程中可以调用Win32 API WaitForSingleObject()函数来监视其状态。
该函数的原型及参数说明如下: 
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
 
其中hHandle为指向要监视的同步对象的句柄,dwMilliseconds为监视hHandle所指向的对象所设置的超时值,单位为毫秒。当在线程的执行函数中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。
如果经过dwMilliseconds毫秒后,hHandle指向的对象变为有信号状态,则WaitForSingleObject()返回,线程被释放,且返回值为WAIT_TIMEOUT;
如果在挂起的dwMilliseconds毫秒内,线程所等待的对象在某一时刻变为有信号,则该函数立即返回,返回值为WAIT_OBJECT_0。
参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。如果CEvent对象为自动事件,则当WaitForSingleObject(hHandle,INFINITE)返回时,自动把CEvent对象重置为无信号状态。CEvent::SetEvent()把对象设置为有信号状态,释放等待的线程。CEvent::ResetEvent()把对象设置为无信号状态,程序在WaitForSingleObject(hHandle,INFINITE)处等待。 
  
例程9 MultiThread9
建立一个基于对话框的工程MultiThread9,在对话框IDD_MULTITHREAD9_DIALOG中加入一个按钮和两个编辑框控件,按钮的ID为IDC_WRITEW,标题为“写‘W’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only; 
在MultiThread9Dlg.h文件中声明两个线程函数: 
UINT WriteW(LPVOID pParam);
UINT WriteD(LPVOID pParam);
使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD; 
在MultiThread9Dlg.cpp文件中添加如下内容: 
为了文件中能够正确使用同步类,在文件开头添加 

#include "afxmt.h"

定义事件对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量。 

CEvent eventWriteD;
char g_Array[10];

添加线程函数: 

UINT WriteW(LPVOID pParam)
{
 CEdit *pEdit=(CEdit*)pParam;
 pEdit->SetWindowText("");
 for(int i=0;i<10;i++)
 {
  g_Array[i]=''W'';
      pEdit->SetWindowText(g_Array);
  Sleep(1000);
 }
 eventWriteD.SetEvent();
 return 0;
}
UINT WriteD(LPVOID pParam)
{
 CEdit *pEdit=(CEdit*)pParam;
 pEdit->SetWindowText("");
 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);
 for(int i=0;i<10;i++)
 {
  g_Array[i]=''D'';
      pEdit->SetWindowText(g_Array);
  Sleep(1000);
 }
 return 0;
}

  仔细分析这两个线程函数, 您就会正确理解CEvent 类。线程WriteD执行到 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);处等待,直到事件eventWriteD为有信号该线程才往下执行,因为eventWriteD对象是自动事件,则当WaitForSingleObject()返回时,系统自动把eventWriteD对象重置为无信号状态。 
双击按钮IDC_WRITEW,添加其响应函数: 

void CMultiThread9Dlg::OnWritew() 
{
 CWinThread *pWriteW=AfxBeginThread(WriteW,
  &m_ctrlW,
  THREAD_PRIORITY_NORMAL,
  0,
  CREATE_SUSPENDED);
 pWriteW->ResumeThread();
 CWinThread *pWriteD=AfxBeginThread(WriteD,
  &m_ctrlD,
  THREAD_PRIORITY_NORMAL,
  0,
  CREATE_SUSPENDED);
 pWriteD->ResumeThread();
 
}

编译并运行程序,单击“写‘W’”按钮,体会事件对象的作用。

if(strartpermission=="2") 
{ 
  document.getElementById('chbPer3').checked = true; 
  typeCheck(2); 
} 
else 

{ 
  document.getElementById('chbPer1').checked = true; 
  typeCheck(0); 
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 转自:http://blog.sina.com.cn/s/blog_5dd35a650100bpqx.html

在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信,除此之外,事件内核对象也可以通过通知操作的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写如下:

// 事件句柄
HANDLE hEvent = NULL;
// 共享资源
char g_cArray[10];
//……
UINT ThreadProc12(LPVOID pParam)
{
 // 等待事件置位
 WaitForSingleObject(hEvent, INFINITE);
 // 对共享资源进行写入操作
 for (int i = 0; i < 10; i++)
 {
  g_cArray[i] = 'a';
  Sleep(1);
 }
 // 处理完成后即将事件对象置位
 SetEvent(hEvent);
 return 0;
}
UINT ThreadProc13(LPVOID pParam)
{
 // 等待事件置位
 WaitForSingleObject(hEvent, INFINITE);
 // 对共享资源进行写入操作
 for (int i = 0; i < 10; i++)
 {
  g_cArray[10 - i - 1] = 'b';
  Sleep(1);
 }
 // 处理完成后即将事件对象置位
 SetEvent(hEvent);
 return 0;
}
//……
void CSample08View::OnEvent()
{
 // 创建事件
 hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 // 事件置位
 SetEvent(hEvent);
 // 启动线程
 AfxBeginThread(ThreadProc12, NULL);
 AfxBeginThread(ThreadProc13, NULL);
 // 等待计算完毕
 Sleep(300);
 // 报告计算结果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
} 

  在创建线程前,首先创建一个可以自动复位的事件内核对象hEvent,而线程函数则通过WaitForSingleObject()等待函数无限等待hEvent的置位,只有在事件置位时WaitForSingleObject()才会返回,被保护的代码将得以执行。对于以自动复位方式创建的事件对象,在其置位后一被WaitForSingleObject()等待到就会立即复位,也就是说在执行ThreadProc12()中的受保护代码时,事件对象已经是复位状态的,这时即使有ThreadProc13()对CPU的抢占,也会由于WaitForSingleObject()没有hEvent的置位而不能继续执行,也就没有可能破坏受保护的共享资源。在ThreadProc12()中的处理完成后可以通过SetEvent()对hEvent的置位而允许ThreadProc13()对共享资源g_cArray的处理。这里SetEvent()所起的作用可以看作是对某项特定任务完成的通知。

  使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到对此事件对象的访问权。可以通过OpenEvent()函数获取得到,其函数原型为:

HANDLE OpenEvent(
 DWORD dwDesiredAccess, // 访问标志
 BOOL bInheritHandle, // 继承标志
 LPCTSTR lpName // 指向事件对象名的指针
); 

  如果事件对象已创建(在创建事件时需要指定事件名),函数将返回指定事件的句柄。对于那些在创建事件时没有指定事件名的事件内核对象,可以通过使用内核对象的继承性或是调用DuplicateHandle()函数来调用CreateEvent()以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一个进程中所进行的线程同步操作是一样的。

  如果需要在一个线程中等待多个事件,则用WaitForMultipleObjects()来等待。WaitForMultipleObjects()与WaitForSingleObject()类似,同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权,任何一个句柄都不可能比其他句柄具有更高的优先权。WaitForMultipleObjects()的函数原型为:

DWORD WaitForMultipleObjects(
 DWORD nCount, // 等待句柄数
 CONST HANDLE *lpHandles, // 句柄数组首地址
 BOOL fWaitAll, // 等待标志
 DWORD dwMilliseconds // 等待时间间隔
); 

  参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在饫锏淖饔糜朐赪aitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAll为TRUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAll为FALSE时)。如果返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间,则表示所有指定对象的状态均为已通知,且其中至少有一个对象是被丢弃的互斥对象(当fWaitAll为TRUE时),或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引(当fWaitAll为FALSE时)。下面给出的代码主要展示了对WaitForMultipleObjects()函数的使用。通过对两个事件内核对象的等待来控制线程任务的执行与中途退出:

// 存放事件句柄的数组
HANDLE hEvents[2];
UINT ThreadProc14(LPVOID pParam)
{
 // 等待开启事件
 DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
 // 如果开启事件到达则线程开始执行任务
 if (dwRet1 == WAIT_OBJECT_0)
 {
  AfxMessageBox("线程开始工作!");
  while (true)
  {
   for (int i = 0; i < 10000; i++);
   // 在任务处理过程中等待结束事件
   DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);
   // 如果结束事件置位则立即终止任务的执行
   if (dwRet2 == WAIT_OBJECT_0 + 1)
    break;
  }
 }
 AfxMessageBox("线程退出!");
 return 0;
}
//……
void CSample08View::OnStartEvent()
{
 // 创建线程
 for (int i = 0; i < 2; i++)
  hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
  // 开启线程
  AfxBeginThread(ThreadProc14, NULL);
  // 设置事件0(开启事件)
  SetEvent(hEvents[0]);
}
void CSample08View::OnEndevent()
{
 // 设置事件1(结束事件)
 SetEvent(hEvents[1]);
} 

  MFC为事件相关处理也提供了一个CEvent类,共包含有除构造函数外的4个成员函数PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分别相当与Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函数。而构造函数则履行了原CreateEvent()函数创建事件对象的职责,其函数原型为:

CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL ); 


  按照此缺省设置将创建一个自动复位、初始状态为复位状态的没有名字的事件对象。封装后的CEvent类使用起来更加方便.B线程在执行到CEvent类成员函数Lock()时将会发生阻塞,而A线程此时则可以在没有B线程干扰的情况下对共享资源进行处理,并在处理完成后通过成员函数SetEvent()向B发出事件,使其被释放,得以对A先前已处理完毕的共享资源进行操作。可见,使用CEvent类对线程的同步方法与通过API函数进行线程同步的处理方法是基本一致的。前面的API处理代码可用CEvent类将其改写为:

// MFC事件类对象
CEvent g_clsEvent;
UINT ThreadProc22(LPVOID pParam)
{
 // 对共享资源进行写入操作
 for (int i = 0; i < 10; i++)
 {
  g_cArray[i] = 'a';
  Sleep(1);
 }
 // 事件置位
 g_clsEvent.SetEvent();
 return 0;
}
UINT ThreadProc23(LPVOID pParam)
{
 // 等待事件
 g_clsEvent.Lock();//或者g_clsEvent.Lock(timeout); timeout单位为毫秒
 // 对共享资源进行写入操作
 for (int i = 0; i < 10; i++)
 {
  g_cArray[10 - i - 1] = 'b';
  Sleep(1);
 }
 return 0;
}
//……
void CSample08View::OnEventMfc()
{
 // 启动线程
 AfxBeginThread(ThreadProc22, NULL);
 AfxBeginThread(ThreadProc23, NULL);
 // 等待计算完毕
 Sleep(300);
 // 报告计算结果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}

原文地址:http://xiaoruizhen1.blog.163.com/blog/static/14174665820114194320790/

原文地址:https://www.cnblogs.com/mqxs/p/3500749.html