C++实现简化 QtBase(5):通过IEventLoopHost扩展实现win32消息循环

在上一篇文章《C++实现简化版Qt的QObject(4):增加简单实用的事件机制》中,我们实现了普通线程的事件机制。
但是事件机制往往需要和操作系统主线程消息循环一起工作。

因此,今天,我们在之前的CEventLoop的实现之上,通过IEventLoopHost扩展,实现一个windows系统的主线程消息循环扩展,使得我们可以在主线程post task。

CEventLoop 代码

首先,昨天我们写的CEventLoop代码如下:

	class CEventLoop {
	public:
		using Clock = std::chrono::steady_clock;
		using TimePoint = Clock::time_point;
		using Duration = Clock::duration;
		using Handler = std::function<void()>;
		struct TimedHandler {
			TimePoint time;
			Handler handler;
			bool operator<(const TimedHandler& other) const {
				return time > other.time;
			}
		};
		class IEventLoopHost {
		public:
			virtual void onPostTask() = 0;
			virtual void onWaitForTask(std::condition_variable& cond, std::unique_lock<std::mutex>& locker) = 0;
			virtual void onEvent(TimedHandler& event) = 0;
			virtual void onWaitForRun(std::condition_variable& cond, std::unique_lock<std::mutex>& locker, const TimePoint& timePoint) = 0;
		};
	private:
		IEventLoopHost* host = nullptr;
		std::priority_queue<TimedHandler> tasks_;
		std::mutex mutex_;
		std::condition_variable cond_;
		std::atomic<bool> running_{ true };

	public:
		void setHost(IEventLoopHost* host) {
			this->host = host;
		}
		void post(Handler handler, Duration delay = Duration::zero()) {
			std::unique_lock<std::mutex> lock(mutex_);
			tasks_.push({ Clock::now() + delay, std::move(handler) });
			cond_.notify_one();
			if (host) {
				host->onPostTask();
			}
		}

		void run() {
			while (running_) {
				std::unique_lock<std::mutex> lock(mutex_);
				if (tasks_.empty()) {
					if (host) {
						host->onWaitForTask(cond_, lock);
					}
					else {
						cond_.wait(lock, [this] { return !tasks_.empty() || !running_; });
					}
				}

				while (!tasks_.empty() && tasks_.top().time <= Clock::now()) {
					auto task = tasks_.top();
					tasks_.pop();
					lock.unlock();
					if (host) {
						host->onEvent(task);
					}
					else {
						task.handler();
					}

					lock.lock();
				}

				if (!tasks_.empty()) {
					if (host) {
						host->onWaitForRun(cond_, lock, tasks_.top().time);
					}
					else {
						cond_.wait_until(lock, tasks_.top().time);
					}
				}
			}
		}

		void stop() {
			running_ = false;
			cond_.notify_all();
		}

	};

实现扩展

在Windows API中,消息循环通常涉及到调用GetMessageTranslateMessageDispatchMessage函数。以下是一个IEventLoopHost的实现,它将Windows消息循环集成到CEventLoop类中:

#include <Windows.h>

class WindowsEventLoopHost : public CEventLoop::IEventLoopHost {
public:
    // 当任务被投递到事件循环时调用
    void onPostTask() override {
        // 可以使用Windows的PostMessage函数发送一个自定义的消息来唤醒消息循环
        PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0);
    }

    // 当事件循环需要等待任务时调用
    void onWaitForTask(std::condition_variable& cond, std::unique_lock<std::mutex>& locker) override {
        locker.unlock();
        MSG msg;
        // 使用PeekMessage而不是GetMessage来避免阻塞,以便定时器事件可以被处理
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        locker.lock();
    }

    // 当事件循环处理事件时调用
    void onEvent(CEventLoop::TimedHandler& event) override {
        // 这里简单地调用事件处理器
        event.handler();
    }

    // 当事件循环需要等待直到特定时间点运行时调用
    void onWaitForRun(std::condition_variable& cond, std::unique_lock<std::mutex>& locker, const CEventLoop::TimePoint& timePoint) override {
        locker.unlock();
        // 计算需要等待的时间
        auto now = CEventLoop::Clock::now();
        auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(timePoint - now).count();
        if (delay_ms > 0) {
            // 设置Windows计时器
            SetTimer(NULL, 0, (UINT)delay_ms, NULL);
            MSG msg;
            // 等待计时器或其他消息
            while (GetMessage(&msg, NULL, 0, 0)) {
                if (msg.message == WM_TIMER) {
                    break; // 计时器消息,继续事件循环
                }
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            KillTimer(NULL, 0);
        }
        locker.lock();
    }
};

在这个实现中,我们通过IEventLoopHost的四个虚函数,完成与Windows消息循环协同工作:

  • onPostTask 使用 PostThreadMessage 来发送一个自定义的消息,以唤醒可能正在等待的消息循环。
  • onWaitForTask 使用 PeekMessage 来处理消息而不阻塞,这样定时器消息可以在适当的时间被处理。
  • onEvent 简单地调用传入的事件处理器,这里我们没有对Windows消息做特别的处理。
  • onWaitForRun 使用 SetTimer 来创建一个Windows计时器,它会在指定的毫秒数后发送一个 WM_TIMER 消息。我们使用 GetMessage 等待这个消息或者其他消息的到来。如果计时器消息到来,我们就终止循环,返回到事件循环,以便继续处理其他任务。

继续优化

在自测过程中进一步的优化代码结构,优化性能,处理WM_QUIT消息,处理一些其他细节等。

最终代码如下:

#include <Windows.h>
class CWindowsEventLoopHost : public base::CEventLoop::IEventLoopHost {
	static constexpr UINT WM_WAKEUP = WM_USER + 15151515;
	UINT_PTR timerID_ = 0;

public:
	~CWindowsEventLoopHost() {
		if (timerID_) {
			KillTimer(NULL, timerID_);
		}
	}

	void onPostTask() override {
		PostThreadMessage(GetCurrentThreadId(), WM_WAKEUP, 0, 0);
	}

	void onWaitForTask(std::condition_variable& cond, std::unique_lock<std::mutex>& locker) override {
		MSG msg;
		while (GetMessage(&msg, NULL, 0, 0)) {
			if (handleWindowsMessage(msg)) {
				break;
			}
			if (cond.wait_for(locker, std::chrono::milliseconds(0), [] { return true; })) {
				break;
			}
		}
	}

	void onEvent(base::CEventLoop::TimedHandler& event) override {
		event.handler();
	}

	void onWaitForRun(std::condition_variable& cond, std::unique_lock<std::mutex>& locker, const std::chrono::steady_clock::time_point& timePoint) override {
		auto now = std::chrono::steady_clock::now();
		if (now < timePoint) {
			auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(timePoint - now).count();
			timerID_ = SetTimer(NULL, 0, (UINT)delay_ms, NULL);//通过timer事件唤醒
			MSG msg;
			while (GetMessage(&msg, NULL, 0, 0)) {
				if (handleWindowsMessage(msg)) {
					break;
				}
			}
		}
	}

	bool handleWindowsMessage(MSG& msg) {
		if (msg.message == WM_QUIT) {
			if (this->eventLoop) {
				this->eventLoop->stop();//退出消息循环
			}
			return true;
		}
		if (msg.message == WM_TIMER && msg.wParam == timerID_) {
			KillTimer(NULL, timerID_);
			timerID_ = 0;
			return true; // Timer event occurred
		}
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		return false;
	}
};

使用示例

由于setHost函数里面没有加锁,而且可能出现在运行过程中被修改host出现不可预期情况。因此,后来去掉setHost函数,改为在CEventLoop的构造函数传入host,避免误用。

使用示例如下:

// 使用示例
int main() {
	CWindowsEventLoopHost host;
	base::CEventLoop loop(&host);

	loop.post([]() {
		std::cout << "Immediate task\n";
		});//马上执行

	loop.post([]() {
		std::cout << "Delayed task\n";
		}, std::chrono::seconds(1));//延时一秒

	loop.post([]() {
		std::cout << "Delayed task in 3 second\n";
		}, std::chrono::seconds(3));//延时3秒

	loop.post([&]() {
		std::cout << "stop msg loop in 6 second\n";
		loop.stop();
		}, std::chrono::seconds(6));//延时6秒

	if (true) {
		loop.run();//主线程直接run
	}
	else {
		// 或者起一个其他线程run:
		std::thread loop_thread([&loop]() {
			loop.run();
			});
		std::this_thread::sleep_for(std::chrono::seconds(10));
		loop.stop();//停止消息循环
		loop_thread.join();
	}
}

完整代码在:https://github.com/kevinyangli/simple_qt_qobject.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/775615.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

12款超良心好用APP推荐,每一款都值得下载!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/分享是奉献的果实&#xff0c;分享是快乐的前提。每天给小伙伴们分享自己认可的软件&#xff0c;也是莫大的幸福&#xff0c;今天获得12款好用的软…

扁鹊三兄弟的启示,保证系统稳定的秘诀

一、稳定性的重要性 1. 公司收益的角度 从公司收益的视角审视&#xff0c;系统不稳定可能会引发直接损失。例如&#xff0c;当系统突然出现故障导致交易中断时&#xff0c;可能造成交易款项的紊乱、资金的滞留或损失&#xff0c;这不但会阻碍当前交易的顺利完成&#xff0c;还…

ASP.NET MVC-razor编写-2-svg中使用js+添加事件监听

环境&#xff1a;win10 效果 初始状态&#xff1a; 鼠标移入某个text&#xff08;比如KS primer&#xff09;时&#xff0c;text和连接的线条与箭头都变色&#xff1a; 鼠标移出时回复正常。 如果是移入另一种红色的text&#xff08;比如Cell Sceening Tag&#xff09;&…

Using a text embedding model locally with semantic kernel

题意&#xff1a;在本地使用带有语义核&#xff08;Semantic Kernel&#xff09;的文本嵌入模型 问题背景&#xff1a; Ive been reading Stephen Toubs blog post about building a simple console-based .NET chat application from the ground up with semantic-kernel. Im…

HexPlane: A Fast Representation for Dynamic Scenes一种动态场景的快速表示方法

Abstract 动态三维场景的建模与再现是三维视觉领域的一个具有挑战性的课题。先前的方法基于 NERF 并依赖于隐式表示这是缓慢的&#xff0c;因为它需要许多 MLP 评估&#xff0c;限制真实世界的应用程序。我们展示了动态三维场景可以明确地表示为六个平面的学习功能&#xff0c…

【重磅】万能模型-直接能换迪丽热巴的模型

万能模型&#xff0c;顾名思义&#xff0c;不用重新训练src&#xff0c;直接可以用的模型&#xff0c;适应大部分原视频脸 模型用法和正常模型一样&#xff0c;但可以跳过训练阶段&#xff01;直接到合成阶段使用该模型 本模型没有做Xseg&#xff0c;对遮挡过多的画面不会自动适…

信创-系统架构师认证

随着国家对信息技术自主创新的战略重视程度不断提升&#xff0c;信创产业迎来前所未有的发展机遇。未来几年内&#xff0c;信创产业将呈现市场规模扩大、技术创新加速、产业链完善和国产化替代加速的趋势。信创人才培养对于推动产业发展具有重要意义。应加强高校教育、建立人才…

2.4章节python中字符串类型

在Python中&#xff0c;字符串&#xff08;String&#xff09;是一种基本的数据类型&#xff0c;用于表示文本信息。字符串可以包含字母、数字、标点符号或任何Unicode字符。Python中的字符串是不可变的&#xff0c;这意味着一旦创建了字符串&#xff0c;就不能更改字符串中的字…

2007年下半年软件设计师【上午题】试题及答案

文章目录 2007年下半年软件设计师上午题--试题2007年下半年软件设计师上午题--答案2007年下半年软件设计师上午题–试题

YOLOV++ 详解 | 网络结构、代码解析、YOLOV 论文阅读、初识 VID

前言 代码地址&#xff1a;https://github.com/YuHengsss/YOLOV 本文网络结构按 YOLOV SwinTiny 绘制&#xff0c;不同的模型主要差异在于 Backbone&#xff0c;VID 相关的部分基本相同。 Predict Input 代码基于 vid_demo。首先会读取视频中的所有帧&#xff08;只能用短视频…

亚马逊跟卖ERP的自动调价功能,能够简易地批量设置价格规则。

跟卖的智能调价 跟卖智能调价简单说是可以上调&#xff0c;下调就是怎么说&#xff1f;上调就是它根靠根据市场最低的价格情况进行去上调。 然后添加指定条件&#xff0c;到工具栏找到指定条件&#xff0c;点击添加指定条件。 然后选择店铺&#xff0c;比如选择店铺&#xf…

p-tuning算法介绍及其pytorch代码实现

P-tuning介绍 代码实现 import torch from transformers import BertTokenizer, BertForSequenceClassification import matplotlib.pyplot as plt import matplotlib.ticker as tickertokenizer BertTokenizer.from_pretrained(bert-base-chinese) model BertForSequenceCl…

Games101学习笔记 Lecture 15: Ray Tracing 3 (Light Transport Global Illumination)

Lecture 15: Ray Tracing 3 (Light Transport & Global Illumination 一、BRDF 双向反射分布函数定义 二、反射方程 Reflection Equation三、渲染方程1.重写反射方程2.当其他的点反射的radiance作为入射 一、BRDF 双向反射分布函数 定义 计算不同的反射方向上会分布多少能…

竹云实力入选《现代企业零信任网络建设应用指南报告》代表性厂商

2024年7月3日&#xff0c;国内网络安全媒体安全牛正式发布《现代企业零信任网络建设应用指南报告(2024版)》。竹云凭借在零信任领域创新性的产品方案和优异的市场表现&#xff0c;实力入选代表性厂商。 伴随着云计算、AI、大数据等技术的发展&#xff0c;远程办公、业务协同、…

遗漏知识点

什么是RAII&#xff1f; RAII是Resource Acquisition Is Initialization&#xff08;wiki上面翻译成 “资源获取就是初始化”&#xff09;的简称&#xff0c;是C语言的一种管理资源、避免泄漏的惯用法。利用的就是C构造的对象最终会被销毁的原则。RAII的做法是使用一个对象&am…

西门子PLC1200--与电脑S7通讯

硬件构成 PLC为西门子1211DCDCDC 电脑上位机用PYTHON编写 二者通讯用网线&#xff0c;通讯协议用S7 PLC上的数据 PLC上的数据是2个uint&#xff0c;在DB1&#xff0c;地址偏移分别是0和2 需要注意的是DB块要关闭优化的块访问&#xff0c;否则是没有偏移地址的 PLC中的数据内…

VCS+Vivado联合仿真BUG

场景&#xff1a; 在vcsvivado联合仿真过程中&#xff0c;对vivado导出的shell脚本修改&#xff0c;修改某些source文件路径&#xff0c;vcs编译时会报Permission Denied。 问题描述 对shell脚本修改如下&#xff1a; 修改仅为注释掉某一行&#xff0c;下面变为source文件新…

【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【20】认证服务04—SSO单点登录

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【20】认证服务04—SSO单点登录 xxl-sso多系统-单点登录单点登录流程原理图单点登录流程简单实现参考 xxl-sso https://gitee.com/xuxueli0323/xxl-sso xxl-sso是开源的一个单点登录框架 …

交换机基本原理

文章目录 前言一、交换机的转发行为二、交换机的转发原理1.MAC地址表2.交换机初始状态3.学习MAC地址4.ARP协议5.交换机转发数据帧6.目标主机回复 三、华为交换机基本命令1.VRP视图分层2.命令行补全3.命令行帮助4.配置设备名称5.命令等级6.用户界面7.配置console认证8.配置用户界…

Ubuntu系统复制文件到共享文件夹出错

1、问题描述 Ubuntu系统复制文件到共享文件夹时&#xff0c;出现拼接文件时出错&#xff1a;输入/输出错误。 使用cp命令&#xff1a; cp -Rf XXX YYY 也是出错&#xff1a; cp: 写入 xxx 出错: 输入/输出错误 2、查看磁盘空间 查看磁盘空间&#xff0c;显示空间还有剩余…