├── ConvenienceQThread.cpp ├── ConvenienceQThread.h └── Readme.txt /ConvenienceQThread.cpp: -------------------------------------------------------------------------------- 1 | ////////1664667894@qq.com|Dai Yorki 2 | #include "ConvenienceQThread.h" 3 | using namespace std; 4 | ConvenienceQThread::~ConvenienceQThread() 5 | { 6 | if (m_is_async_delete == false) 7 | { 8 | cancelAllTask(); 9 | disconnect(m_callback_executer); 10 | delete m_callback_executer; 11 | wait(); // 等待线程结束再删除线程 12 | } 13 | 14 | } 15 | ConvenienceQThread::ConvenienceQThread(QThread* parent) :QThread(parent) { 16 | m_callback_executer = new QObject(); 17 | m_child_ThreadID = this->currentThreadId(); 18 | m_callback_executer->connect(this, &ConvenienceQThread::pleaseExecuteCallback, m_callback_executer, [=]() { 19 | while (1) 20 | { 21 | //1.主线程前检查 22 | m_boiled_mutex.lock(); 23 | auto first_element = m_boiled_tasks.begin(); 24 | if (first_element == m_boiled_tasks.end()) 25 | { 26 | m_boiled_status = THREAD_IDLE; 27 | m_boiled_mutex.unlock(); 28 | return; 29 | } 30 | if (m_boiled_status == TASK_NEED_TO_BEEN_STOP) 31 | { 32 | m_boiled_tasks.erase(first_element); 33 | m_boiled_status = THREAD_IDLE; 34 | m_boiled_mutex.unlock(); 35 | return; 36 | } 37 | int task_ID = first_element->first; 38 | std::functionmain_thread_task = first_element->second; 39 | m_boiled_tasks.erase(first_element); 40 | m_boiled_status = task_ID; 41 | m_boiled_mutex.unlock(); 42 | //1.主线程前检查结束 43 | main_thread_task(); 44 | //2.主线程后检查 45 | m_boiled_mutex.lock(); 46 | m_boiled_status = THREAD_IDLE; 47 | m_boiled_mutex.unlock(); 48 | //2.主线程后检查结束 49 | } 50 | }); 51 | } 52 | int ConvenienceQThread::asyncTask(std::functiontask, std::function async_callback) 53 | { 54 | m_raw_mutex.lock(); 55 | m_uuid_maker++; 56 | m_raw_tasks.insert(make_pair(m_uuid_maker.load(), make_pair(task, async_callback))); 57 | m_raw_mutex.unlock(); 58 | start(); 59 | return m_uuid_maker.load(); 60 | } 61 | int ConvenienceQThread::syncTask(std::functiontask, std::function sync_callback) 62 | { 63 | m_uuid_maker++; 64 | task(); 65 | sync_callback(); 66 | return m_uuid_maker.load(); 67 | } 68 | void ConvenienceQThread::run() 69 | { 70 | while (1) 71 | { 72 | //1.子线程前检查 73 | m_raw_mutex.lock(); 74 | auto first_element = m_raw_tasks.begin(); 75 | 76 | if (first_element == m_raw_tasks.end()) { 77 | m_raw_status = THREAD_IDLE; 78 | m_raw_mutex.unlock(); 79 | return; 80 | } 81 | m_raw_tasks.erase(first_element); 82 | int taskID = first_element->first; 83 | if (m_raw_status == TASK_NEED_TO_BEEN_STOP) 84 | { 85 | m_raw_status = THREAD_IDLE; 86 | m_raw_mutex.unlock(); 87 | if (m_is_async_delete && taskID == m_delete_task_uuid) 88 | emit deleteFinish(); 89 | return; 90 | } 91 | std::functionchild_thread_task = first_element->second.first; 92 | std::functionmain_thread_task = first_element->second.second; 93 | 94 | m_raw_status = taskID; 95 | m_raw_mutex.unlock(); 96 | //1.子线程前检查结束 97 | child_thread_task(); 98 | //2.子线程后检查 99 | 100 | m_raw_mutex.lock(); 101 | if (m_raw_status < 0) { 102 | m_raw_status = THREAD_IDLE; 103 | m_raw_mutex.unlock(); 104 | if (m_is_async_delete && taskID == m_delete_task_uuid) 105 | emit deleteFinish(); 106 | return; 107 | 108 | } 109 | m_raw_status = THREAD_IDLE; 110 | m_raw_mutex.unlock(); 111 | //2.子线程后检查结束 112 | 113 | //3.插入主线程 114 | m_boiled_mutex.lock(); 115 | m_boiled_tasks.insert(make_pair(taskID, main_thread_task)); 116 | m_boiled_mutex.unlock(); 117 | emit pleaseExecuteCallback(taskID); 118 | if (m_is_async_delete && taskID == m_delete_task_uuid) 119 | emit deleteFinish(); 120 | } 121 | } 122 | void ConvenienceQThread::asyncDelete() { 123 | m_is_async_delete = true; 124 | cancelAllTask(); 125 | disconnect(m_callback_executer); 126 | delete m_callback_executer; 127 | m_delete_task_uuid = m_uuid_maker.load() + 1; 128 | asyncTask([]() {}, []() {}); 129 | } 130 | void ConvenienceQThread::addImportantMark(const int uuid) { 131 | m_raw_mutex.lock(); 132 | m_boiled_mutex.lock(); 133 | m_important_tasks.insert(uuid); 134 | m_boiled_mutex.unlock(); 135 | m_raw_mutex.unlock(); 136 | }; 137 | void ConvenienceQThread::cancelTask(const int uuid) { 138 | m_raw_mutex.lock(); 139 | m_raw_tasks.erase(uuid); 140 | if (m_raw_status == uuid) m_raw_status = TASK_NEED_TO_BEEN_STOP; 141 | m_raw_mutex.unlock(); 142 | 143 | m_boiled_mutex.lock(); 144 | m_boiled_tasks.erase(uuid); 145 | if (m_boiled_status == uuid) m_boiled_status = TASK_NEED_TO_BEEN_STOP; 146 | m_boiled_mutex.unlock(); 147 | } 148 | void ConvenienceQThread::cancelAllTask() { 149 | m_raw_mutex.lock(); 150 | if (m_raw_status >= 0 && m_important_tasks.find(m_raw_status) == m_important_tasks.end())m_raw_status = TASK_NEED_TO_BEEN_STOP; 151 | for (auto it = m_raw_tasks.begin(); it != m_raw_tasks.end(); ) { 152 | if (m_important_tasks.find(it->first) == m_important_tasks.end()) { 153 | it = m_raw_tasks.erase(it); 154 | } 155 | else { 156 | ++it; 157 | } 158 | } 159 | m_raw_mutex.unlock(); 160 | 161 | m_boiled_mutex.lock(); 162 | if (m_boiled_status >= 0 && m_important_tasks.find(m_boiled_status) == m_important_tasks.end())m_boiled_status = TASK_NEED_TO_BEEN_STOP; 163 | for (auto it = m_boiled_tasks.begin(); it != m_boiled_tasks.end(); ) { 164 | if (m_important_tasks.find(it->first) == m_important_tasks.end()) { 165 | it = m_boiled_tasks.erase(it); 166 | } 167 | else { 168 | ++it; 169 | } 170 | } 171 | m_boiled_mutex.unlock(); 172 | } 173 | bool ConvenienceQThread::ConvenienceQThread::checkEligible() { 174 | if (QThread::currentThreadId() == m_child_ThreadID) 175 | { 176 | if (m_boiled_status >= 0)return true; 177 | return false; 178 | } 179 | else 180 | { 181 | if (m_raw_status >= 0)return true; 182 | return false; 183 | } 184 | } 185 | //如你有兴趣,可将类改成线程池.理论上可以不用std::map而用哈希表和链表达成o(1)复杂度 186 | -------------------------------------------------------------------------------- /ConvenienceQThread.h: -------------------------------------------------------------------------------- 1 | ////////1664667894@qq.com|Dai Yorki 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | class ConvenienceQThread : public QThread 8 | { 9 | Q_OBJECT 10 | public: 11 | /*外部接口1:添加任务接口*/ 12 | int asyncTask(std::functiontask, std::function async_callback); 13 | //主接口,方便的将同步代码转成异步代码。第一个调用对象为QThread执行,第二个这个类所在线程执行的回调 14 | int syncTask(std::functiontask, std::function sync_callback);//同步测试代码 15 | 16 | /*外部接口2:任务管理接口,如你不满意可自行添加*/ 17 | void cancelTask(const int uuid);//删除指定任务,线程安全 18 | void cancelAllTask();//删除所有任务,线程安全 19 | void addImportantMark(const int uuid); 20 | //使一个任务为重要任务,无法被cancelAllTask撤单 21 | //比如网络通信中用户可能有“清除所有的发送数据task,但保留接受数据task”的需求 22 | 23 | /*外部接口3,删除接口,异步*/ 24 | void asyncDelete();//请连接 deleteFinish信号 25 | //这个类是个QThread子类,在你删除它之前,请先调用这个函数将将其disable掉, 26 | //这个操作是异步的,异步删除思路为主动取消所有待执行任务,再断开回调执行对象。 27 | //最后查询当前任务自增ID,添加一个空任务,当这个空任务执行完成后,发送deleteFinish信号。 28 | //然后,可以按对未执行的QThread的方式删除对象。 29 | 30 | /*外部接口4,用户的异步函数和回调函数调用,检查当前任务是否应该被提前终止*/ 31 | bool checkEligible(); 32 | 33 | ConvenienceQThread(QThread* parent = nullptr); 34 | ~ConvenienceQThread();/*对象可直接delete不崩溃,但不推荐,推荐用asyncDelete*/ 35 | private: 36 | 37 | const int THREAD_IDLE = -1; 38 | const int TASK_NEED_TO_BEEN_STOP = -2; 39 | QObject* m_callback_executer; 40 | std::setm_important_tasks; 41 | std::atomic m_uuid_maker = 0; 42 | 43 | std::shared_mutex m_raw_mutex;//注意,这个先锁 44 | std::map, std::function>> m_raw_tasks; 45 | int m_raw_status = THREAD_IDLE; 46 | 47 | std::shared_mutex m_boiled_mutex;//注意,这个后锁 48 | std::map> m_boiled_tasks; 49 | int m_boiled_status = THREAD_IDLE; 50 | 51 | Qt::HANDLE m_child_ThreadID; 52 | bool m_is_async_delete = false; 53 | int m_delete_task_uuid = 0; 54 | protected: 55 | void run() override; 56 | signals: 57 | void pleaseExecuteCallback(int uuid);//请勿使用,这个信号是类内部使用的 58 | void deleteFinish();//当调用asyncDelete异步删除接口后,准备删除工作完成时会发送这个信号 59 | }; 60 | -------------------------------------------------------------------------------- /Readme.txt: -------------------------------------------------------------------------------- 1 | 在c++/qt中使用c#的async/await语法糖 2 | 简介 3 | c++/qt编程中,经常有模糊需求“这段代码太慢了,将它移到多线程中”。本框架使用一个大括号标识异步代码段,另一个的大括号标识事件回调代码段,就像C# 中的 async/await 机制一样,使业务开发人员用同步代码的逻辑开发异步代码。并包含任务管理与线程的安全删除接口。 4 | 5 | 示例 6 | 以下是一个简单的使用示例,用于异步执行某段代码。通过添加一行标识符,使函数的上半部分变为异步,下半部分为同步,在不改变代码结构的前提下,将同步改为异步。 7 | 8 | void Test::SyncCalculate() { //模拟某同学代码风格及其使用方式 9 | m_pi = 计算圆周率(); 10 | setText(m_pi); 11 | } 12 | void Test::ASyncCalculate() { 13 | int taskID=easyThread->asyncTask([=]() { 14 | m_pi = 计算圆周率(); 15 | }, [=]() { 16 | setText(m_pi); 17 | }); 18 | } 19 | 在传统的多线程编程中,首先继承QThread类并重载run方法,然后附加自定义信号,最后链接信号槽调用,此流程相对繁琐。我们将这一流程的关键部分抽象出来,用std::function配合std容器存储可调用对象,并由 QThread线程执行,在任务完成后将回调任务插入调用方事件循环中,以实现异步编程。这个思路也同样适用于其他带事件回调的框架,如libevent和OSG。因为将事件直接插入了事件循环所以无需调用方主动管理,比c++20的coroutine更加新手友好。 20 | 21 | 进阶功能:任务管理与线程的安全删除 22 | 在实际应用中,用户常常需要对尚未执行异步任务进行管理,例如查询的幕等性或者撤销查询;C++/Qt 中直接删除一个正在运行的线程对象后子线程仍会执行,可能造成潜在的bug,这是与业务开发无关的语言特性。为了解决这一问题,通过成员变量标识任务状态的方式,我们提供了任务管理接口。同时实现了对象同步delete不崩溃,并提供了异步删除接口。 23 | 24 | 开发理念 25 | 现在是chatgpt的时代了,并且我们开源的目标就是初级开发人员或是边缘业务。所以我尽量的简化逻辑并去除了实际使用中的远程RPC多路复用、线程池模式,确保gpt和仅6有月开发经验的同事能看懂。 26 | 由于人员预算限制,业务变迁等原因,我们不能仅接受高级开发人员或忽略时间的投入产出比。希望这个框架或思路对您的业务开发有所帮助。 27 | --------------------------------------------------------------------------------