最近工作中要维护一个windows模块,用到了mfc中的CEvent类。这算是很久很久以前的老朋友了吧,估计和我超过10年没见过面了,不过工作就是工作,技术上来不得半点含糊,所以还是重新认识一下这位老朋友吧。

本文用一个具体的例子来对CEvent类进行介绍,基本上掌握了这个例子后,我们就算是彻底认识CEvent类了。其实其它windows多线程同步的内核对象也大体如此,这是一帮老朋友们。

1.CEvent类

CEvent的接口很少:

基类就更简单了:

其实CEvent类只是对原生的Windows API的一层很浅的封装。这可以从它的构造函数源代码中轻易的看出来:

CEvent::CEvent(BOOL bInitiallyOwn, BOOL bManualReset, LPCTSTR pstrName,
LPSECURITY_ATTRIBUTES lpsaAttribute)
: CSyncObject(pstrName)
{
m_hObject = ::CreateEvent(lpsaAttribute, bManualReset,
bInitiallyOwn, pstrName);
if (m_hObject == NULL)
AfxThrowResourceException();
}

2.测试程序

既然要用MFC,测试用例当然是带界面的了:

如果有足够老的程序员应该对这个界面不会陌生,它和侯捷的那本《win32多线程程序设计》中的CEvent例子很象。这本书实在是太老了,侯捷的代码写得也谈不上漂亮,所以我干脆动手重新撸了一个。

测试程序演示了CEvent的两种模式:自动模式和手动模式,并分别对几个类方法进行了测试。

这是一个标准的MFC对话框程序,开发工具用VS2017:

怎么通过向导建立工程?怎么在资源里拖放控件?怎么建立消息映射等等太简单了,我就跳过了,下面将主要讲解主窗口的CEventDemoDlg类。

3.准备工作

在本测试程序中,界面的开发是次要的,主要是多线程的开发,下面进行一些准备工作。

核心对象的创建和销毁:

void CEventDemoDlg::InitEvent(BOOL bManualReset)
{
m_event = new CEvent(FALSE, bManualReset, _T("EventDemoEvent"));
} void CEventDemoDlg::ExitEvent()
{
if (m_event != NULL)
{
delete m_event;
}
}

再给个公共的访问方法:

    CEvent* event()
    {
        return m_event;
    }

工作线程:

先简单设计一下工作线程的持有数据:

    struct ThreadData
{
int id;
CEventDemoDlg* dialog;
CWinThread* thread;
};

id用于标识线程;dialog记录各个线程的访问资源;thread主要是为了处理线程的退出。

然后是工作线程:

UINT  AFX_CDECL workThread(LPVOID lpParam)
{
CEventDemoDlg::ThreadData* threadData = (CEventDemoDlg::ThreadData*)lpParam;
int id = threadData->id;
CEventDemoDlg* dialog = threadData->dialog; while (true)
{
DWORD ret = WaitForSingleObject(dialog->event()->m_hObject, INFINITE); if (dialog->isExitThread())
{
break;
} CString message;
message.Format(_T("thread %d write %d"), id, ret); dialog->SendMessage(WM_CUSTUM_WRITE_RESULT, (WPARAM)message.AllocSysString(), ); Sleep();
} return ;
}

工作线程蛮简单,主要是等待核心对象,等到后就发送一个消息到主对话框。注意这里发送的消息内容应该用AllocSysString在堆中分配,因为工作线程本身一跑起来就如脱缰的野马,并不适合持有消息内容。

关于自定义windows消息和消息内容的界面显示,都没啥难度:

#define WM_CUSTUM_WRITE_RESULT WM_APP + 100

    ON_MESSAGE(WM_CUSTUM_WRITE_RESULT, &CEventDemoDlg::OnWriteResult)

LRESULT CEventDemoDlg::OnWriteResult(WPARAM wParam, LPARAM lParam)
{
BSTR param = (BSTR)wParam;
CString message(param);
SysFreeString(param); m_result.AddString(message); int count = m_result.GetCount();
if (count > )
{
m_result.SetCurSel(count - );
} return ;
}

我们想让工作线程可以优雅的退出,所以这里加了一个isExitThread标记。

工作线程的创建:

void CEventDemoDlg::InitThread()
{
m_isExitThread = false; for (int i = ; i < ; i++)
{
ThreadData* threadData = new ThreadData;
m_threadDatas.push_back(threadData);
threadData->id = i;
threadData->dialog = this;
threadData->thread = AfxBeginThread(workThread, (LPVOID)threadData, THREAD_PRIORITY_NORMAL, , CREATE_SUSPENDED);
threadData->thread->ResumeThread();
}
}

这里暂定3个工作线程,实际线程个数应该根据业务来,或许还要定义一个函数,这里简化了,就直接用这个魔数吧。

因为工作线程个数实际上并不固定,所以相应的ThreadData也是动态分配的。

工作线程的销毁:

void CEventDemoDlg::ExitThread()
{
m_isExitThread = true; size_t count = m_threadDatas.size();
HANDLE* threads = new HANDLE[count];
for (size_t i = ; i < count; i++)
{
ThreadData* threadData = m_threadDatas[i];
m_event->SetEvent();
threads[i] = threadData->thread->m_hThread;
} WaitForMultipleObjects(DWORD(count), threads, TRUE, INFINITE); delete[] threads; for (size_t i = ; i < count; i++)
{
ThreadData* threadData = m_threadDatas[i];
delete threadData;
}
m_threadDatas.clear();
}

首先设置了m_isExitThread退出标记,但是千万别以为设置了这个标记工作线程就会真的退出,那就大错特错了,因为工作线程可能正处在等待的假死状态,是不会进行标记判断的。所以下一步要循环挨个唤醒这帮家伙,这样它们就能优雅的退出了。

实际线程退出的时间是无法确定的,所以这里用WaitForMultipleObjects来进行多个核心对象的等待,以确保这帮慢腾腾的老家伙确实是优雅的落幕了。

最后清除线程的持有数据。

4.自动模式

我们可以把CEvent比喻成一道食堂的大门,工作线程比喻成打饭的程序员。那么SetEvent就是开门,可以打饭;ResetEvent就是关门,不可以打饭。

那么什么是自动模式呢?你可以理解成这是一道带电子锁的智能大门,所谓的自动的意思就是它打开后会立即自动关门。

初始化环境,在对话框的OnInitDialog中我们有下面的初始化处理:

    CButton* radio = (CButton*)GetDlgItem(IDC_RADIO_AUTOMATIC);
radio->SetCheck(TRUE); InitEvent(FALSE); InitThread();

在界面上打上自动模式的标记。

初始化核心对象,这里的FALSE表示是自动模式。此时门是关着的。

初始化工作线程。这些家伙都在门口等着吃饭。

下面是开门:

void CEventDemoDlg::OnBnClickedButtonSetEvent()
{
// TODO: 在此添加控件通知处理程序代码
m_event->SetEvent();
}

没错,一次就放进来一个人吃饭。因为是自动模式,开门后刚进来一个人,门就自动关上了。

点击三次后:

点了三次才进来三个人,就是这么费劲。所以,你大可把自动模式想象成曾今的国营饭店,一次只能服务一桌客人。

关门,点击ResetEvent:

void CEventDemoDlg::OnBnClickedButtonResetEvent()
{
// TODO: 在此添加控件通知处理程序代码
m_event->ResetEvent();
}

没有任何反应。因为自动模式是自带关门功能的。

点击PulseEvent:

void CEventDemoDlg::OnBnClickedButtonPulseEvent()
{
// TODO: 在此添加控件通知处理程序代码
m_event->PulseEvent();
}

pulse是脉冲的意思,这代表一次放进去一波客人,不过在自动模式下,因为门关得太快,一次也只能一个客人。所以这个效果和点击SetEvent是一样的。

点击PulseEvent三次后:

5.手动模式

在界面上切换到手动模式:

void CEventDemoDlg::OnBnClickedRadioManual()
{
// TODO: 在此添加控件通知处理程序代码
ExitThread(); ExitEvent();
InitEvent(TRUE); InitThread();
}

首先销毁了工作线程,销毁了核心对象。然后重建新的核心对象和工作线程。

这里的TRUE表示是手动模式。此时门是关着的。

初始化工作线程。这些家伙都在门口等着吃饭。

下面点击SetEvent开门:

void CEventDemoDlg::OnBnClickedButtonSetEvent()
{
// TODO: 在此添加控件通知处理程序代码
m_event->SetEvent();
}

大门一开,工作线程们果然如脱缰的野马般跑个不停。

赶紧点击关门:

void CEventDemoDlg::OnBnClickedButtonResetEvent()
{
// TODO: 在此添加控件通知处理程序代码
m_event->ResetEvent();
}

工作线程们终于停下来了。

点击Clear Result清理一下狼藉的现场。

void CEventDemoDlg::OnBnClickedButtonClearResult()
{
// TODO: 在此添加控件通知处理程序代码
m_result.ResetContent();
}

这次试一试点击PulseEvent:

void CEventDemoDlg::OnBnClickedButtonPulseEvent()
{
// TODO: 在此添加控件通知处理程序代码
m_event->PulseEvent();
}

这就是脉冲的意思,一次将门口正在等待的一波工作线程统统放进来,然后关门。

5.后记

CEvent是Windows系统特有的一种线程同步的核心对象,个人感觉设计得有些复杂了。但不可否认,正是因为它的多面性,在实际开发中,它的出场几率可是相当高的。能把这个同步的核心对象用好的程序员,其它的几个同步的核心对象就通通不在话下了。

多线程之CEvent的更多相关文章

  1. iOS多线程之8.NSOPeration的其他用法

      本文主要对NSOPeration的一些重点属性和方法做出介绍,以便大家可以更好的使用NSOPeration. 1.添加依赖 - (void)addDependency:(NSOperation * ...

  2. python 线程之 threading(四)

    python 线程之 threading(三) http://www.cnblogs.com/someoneHan/p/6213100.html中对Event做了简单的介绍. 但是如果线程打算一遍一遍 ...

  3. python 线程之 threading(三)

    python 线程之 threading(一)http://www.cnblogs.com/someoneHan/p/6204640.html python 线程之 threading(二)http: ...

  4. python 线程之_thread

    python 线程之_thread _thread module: 基本用法: def child(tid): print("hello from child",tid) _thr ...

  5. Java多线程之ConcurrentSkipListMap深入分析(转)

    Java多线程之ConcurrentSkipListMap深入分析   一.前言 concurrentHashMap与ConcurrentSkipListMap性能测试 在4线程1.6万数据的条件下, ...

  6. 【C#】线程之Parallel

    在一些常见的编程情形中,使用任务也许能提升性能.为了简化变成,静态类System.Threading.Tasks.Parallel封装了这些常见的情形,它内部使用Task对象. Parallel.Fo ...

  7. iOS多线程之GCD小记

    iOS多线程之GCD小记 iOS多线程方案简介 从各种资料中了解到,iOS中目前有4套多线程的方案,分别是下列4中: 1.Pthreads 这是一套可以在很多操作系统上通用的多线程API,是基于C语言 ...

  8. 多线程之RunLoop

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  9. 多线程之join方法

    join方法的功能就是使异步执行的线程变成同步执行.也就是说,当调用线程实例的start方法后,这个方法会立即返回,如果在调用start方法后后需要使用一个由这个线程计算得到的值,就必须使用join方 ...

随机推荐

  1. [Python]sort与sorted高级技巧

    与其他语言不同,python 3.0之后,弃用了其他语言中常见的cmp方法,在sort方法中改用key实现. 之前一直疑惑自定义对象的排序如何写comparator,最后发现还是通过内部的__cmp_ ...

  2. Java笔记(二十) 注解

    注解 一.内置注解 Java内置了一些常用注解: 1.@Override 该注解修饰一个方法,表示当前类重写了父类的该方法. 2.@Deprecated  该注解可以修饰类.方法.字段.参数等.表示对 ...

  3. three.js 使用DragControls.js 拖动元素

    首先,引入js文件: <script type="text/javascript" src="./path/to/DragControls.js"> ...

  4. js高级的2

    BOM0级事件元素绑定多个click最后只执行最后一个click. DOM2级事件元素绑定多个click,都要执行 注意当绑定的多个事件名,函数名,事件发生阶段三者完全一样时,才执行最后一个 div. ...

  5. PAT甲级1078 Hashing【hash】

    题目:https://pintia.cn/problem-sets/994805342720868352/problems/994805389634158592 题意: 给定哈希表的大小和n个数,使用 ...

  6. 01背包 || BZOJ 1606: [Usaco2008 Dec]Hay For Sale 购买干草 || Luogu P2925 [USACO08DEC]干草出售Hay For Sale

    题面:P2925 [USACO08DEC]干草出售Hay For Sale 题解:无 代码: #include<cstdio> #include<cstring> #inclu ...

  7. 20165311 《网络对抗技术》 Kali安装

    一.目的要求 下载 安装 网络 共享 软件源 二.主要步骤 从官网下载软件安装包 安装的比较顺利 具体截图就不放上来了 安装结果图: 网络配置和共享文件夹设置 网络配置: 由于我之前安装虚拟机,所以并 ...

  8. Express全系列教程之(三):get传参

    一.关于get请求 一般在网站开发中,get都用作数据获取和查询,类似于数据库中的查询操作,当服务器解析前台资源后即传输相应内容:而查询字符串是在URL上进行的,形如: http://localhos ...

  9. Django2.0.4 + websocket 实现实时通信,主动推送,聊天室及客服系统

    webSocket是一种在单个TCP连接上进行全双工通信的协议. webSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在WebSocket API中,浏览器 ...

  10. 微信小程序字符串替换

    字符串替换有两种,一种是替换一个子字符串,一种是将子字符串全部替换 替换一个子字符串 要求:将“ ”(空格)替换成“,” var isguestnumbername=“aaa bbb ccc” isg ...