# AsyncLib **Repository Path**: kanggaojie/AsyncLib ## Basic Information - **Project Name**: AsyncLib - **Description**: 基于C++20开发的Windows平台的协程库。 - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-11-13 - **Last Updated**: 2025-07-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # AsyncLib 一个带异步任务的C++20协程库,它包含两部分:协程、异步任务。 协程:基于C++20封装的协程库,本项目中已经与异步任务整合,也可以将其它异步库封装成协程版本,例如:异步Http、异步IPC等等。 协程的概念可以参考`C#`语言的`async/await`。 异步任务:在C++20前没有协程,异步任务可以单独使用,但在与协程机制整合之后,可以更方便的等待异步任务,并获取其返回值。 ## 编译使用 本项目可以支持跨平台,但目前只在Windows平台上实现,编译器要求支持C++20。 如果在Visual Studio中使用只需2步,首先将`AsyncLib.h`包含到你的项目中,然后将`AsyncLib.lib`链接到你的项目中,这步可以使用**项目引用**。 请在主线程开始时调用`AsyncLibInit()`,结束时调用`AsyncLibExit()`。 本库提供的所有功能都在`AL`这个命名空间中,可以使用`using namespace AL;`来简化写法。 ## 线程分类 在异步任务中有3类线程:主线程(UI线程)、任务线程、线程池。 任务线程有唯一`ATID`,主线程的为`ATID_MAIN`,其它的需要自定义,例如`ATID_DB`、`ATID_IPC`。可以给指定的任务线程发送任务,它们会在指定线程中**顺序执行**。 主线程任务一般用于执行UI操作。 在自己项目中定义其它ATID,从2开始且是连续的,`AsyncLibInit`时要传入`ATID_MAX`。 ```C++ constexpr ATID ATID_DB (2); constexpr ATID ATID_IPC(3); constexpr ATID ATID_MAX = ATID_IPC; ``` ## 用法例子 ### class `Task` 模板类`Task`是协程函数和异步任务的返回类型,`Task`可简写为`Task<>`,可以使用`co_await`异步获取结果,有以下成员函数。 #### bool Wait(); 同步等待任务完成,返回`false`代表任务被取消。 #### T Result(); 获取任务的结果,在`Wait()`返回`true`之后才能调用。 #### void Forget(); 忽略此任务中的异常,否则需要使用`co_await`或`Wait()`捕获。 ```C++ task1.Forget(); try { co_await task2; } catch (...) { } try { task3.Wait(); } catch (...) { } ``` #### Task CaptureThread(); 记住协程挂起前的线程ID,恢复时自动切换回来,下面有详细用法。 ### 发送任务 #### Task PostTask(ATID atid, ...); ```C++ // 向主线程发送任务 PostTask(ATID_MAIN, [] { DoSomething(123); }); PostTask(ATID_DB, DoSomething, 123); ``` #### Task PostTask(...); ```C++ // 向线程池发送任务 PostTask([] { DoSomething(123); }); PostTask(DoSomething, 123); // 使用 co_await 获取结果 int res1 = co_await PostTask([] { DoSomething(123); return 12; }); // lambda 是一个协程 int res2 = co_await PostTask([]() -> Task { Sleep(1000); co_await DelayAsync(1000); co_return 12; }); ``` #### Task PostDelayedTask(UINT milliseconds, ...); ```C++ // 向线程池发送延迟任务 PostDelayedTask(1000, [] { DoSomething(123); }); ``` ### 切换线程 #### Task<\> SwitchToMainThread(); ```C++ // 切换到主线程 assert(!IsInMainThread()); co_await SwitchToMainThread(); assert(IsInMainThread()); ``` #### Task<\> SwitchToTaskThread(ATID atid); ```C++ // 切换到任务线程 assert(IsInThreadPool()); co_await SwitchToTaskThread(ATID_DB); assert(IsInTaskThread(ATID_DB)); ``` #### Task<\> SwitchToThreadPool(); ```C++ // 切换到线程池 assert(!IsInThreadPool()); co_await SwitchToThreadPool(); assert(IsInThreadPool()); ``` #### Task<\> DelayAsync(UINT milliseconds); ```C++ // 延迟1000毫秒后切换到线程池 assert(IsInMainThread()); co_await DelayAsync(1000); assert(IsInThreadPool()); ``` ### 捕获线程 使用`Task`的`CaptureThread()`函数可以记住当前线程ID,使协程恢复时回到原来线程中。 例如: ```C++ // 不使用 CaptureThread assert(IsInMainThread()); co_await PostTask([] { DoSomething(123); }); assert(IsInThreadPool()); ``` ```C++ // 使用 CaptureThread assert(IsInMainThread()); co_await PostTask([] { DoSomething(123); }).CaptureThread(); assert(IsInMainThread()); co_await DelayAsync(1000).CaptureThread(); assert(IsInMainThread()); ``` **注意:**`SwitchToThreadPool()`等函数不能使用`CaptureThread()`,因为它们就是要切换线程。 ### 异步等待 #### Task WaitForAsync(HANDLE handle, UINT milliseconds); 返回值参考`WaitForSingleObject` ```C++ // 等待一个事件 DWORD dwResult = co_await WaitForAsync(hEvent, INFINITE); ``` #### Task<\> TaskWhenAll(Task task, ...); ```C++ // 等待所有任务完成 Task task1 = FooAsync(); Task task2 = BarAsync(); co_await TaskWhenAll(task1, task2); // 然后两种方法获取结果 bool v1 = task1.Result(); int v2 = co_await task2; ``` #### Task TaskWhenAny(Task task, ...); ```C++ // 等待任意任务完成并返回索引 size_t index = co_await TaskWhenAny(DelayAsync(1000), DelayAsync(2000)); assert(index == 0); // 未完成的任务不能 task.Result() ``` #### bool MsgWaitForTask(Task task); 在控制台应用的主线程中使用消息循环来等待一个任务,以支持`ATID_MAIN`任务线程。 #### class `CAsyncMutex` 异步互斥量,需要配合`CSharedLock`或`CUniqueLock`使用,类似于`std::mutex`和`std::unique_lock`。 ```C++ CAsyncMutex m_mutex; CUniqueLock lock(m_mutex); co_await lock.LockAsync(); ``` ### 封装其它库 #### Task WrapFnAsAsync(Fn fn, ...); 此函数可以将其它回调版本的异步函数,封装成协程版本。假设HttpGet来自其它库: ```C++ // 回调版本的异步请求 void HttpGet(LPCWSTR lpUrl, LPCWSTR lpHeaders, std::function&& fnCallback); // 调用方式如下(回调地狱) HttpGet(L"http://aaa", nullptr, [](LPCSTR lpData, int nSize) { printf("aaa size=%d\n", nSize); HttpGet(L"http://bbb", nullptr, [](LPCSTR lpData, int nSize) { printf("bbb size=%d\n", nSize); }); }); ``` ```C++ // 封装成协程版本 Task HttpGetAsync(LPCWSTR lpUrl, LPCWSTR lpHeaders) { return WrapFnAsAsync(HttpGet, lpUrl, lpHeaders); } // 调用方式如下 std::string str1 = co_await HttpGetAsync(L"http://aaa", nullptr); printf("aaa size=%d\n", str1.size()); std::string str2 = co_await HttpGetAsync(L"http://bbb", nullptr); printf("bbb size=%d\n", str2.size()); ``` ### 其它杂项 #### class `CUiTimer` 主线程定时器,有`Start`和`Stop`函数,回调函数运行在主线程中。 #### class `CTpTimer` 线程池定时器,有`Start`和`Stop`函数,回调函数运行在线程池中。 #### class `CAsyncFile` 异步文件,有`CreateAsync`、`ReadAsync`和`WriteAsync`函数,`ReadAllAsync`和`WriteAllAsync`静态函数。 ## 最佳实践 ### 如何将控制台应用的main()函数变为协程? ```C++ Task Foo(); Task Bar(); Task DoSomething() { double val = co_await Bar(); co_return std::to_string(val); } Task MainAsync(int argc, char *argv[]) { if (!co_await Foo()) co_return -1; std::string str = co_await DoSomething(); printf("str=%s\n", str.c_str()); co_return 0; } int main(int argc, char *argv[]) { AsyncLibInit(); auto task = MainAsync(argc, argv); int result = MsgWaitForTask(task) ? task.Result() : 0; AsyncLibExit(); return result; } ``` ### 基类中定义的接口是异步的,派生类如何以同步实现? ```C++ class CBase { public: virtual Task<> Func1() = 0; virtual Task Func2() = 0; }; class CDerived1 : public CBase { public: // Asynchronous functions Task<> Func1() override { co_await DelayAsync(1000); } Task Func2() override { co_await DelayAsync(1000); co_return 2; } }; class CDerived2 : public CBase { public: // Synchronous functions Task<> Func1() override { Sleep(1000); return CompletedTask(); } Task Func2() override { Sleep(1000); return TaskFromResult(2); } }; ``` ### 如何改造遗留代码中创建线程和消息窗口的逻辑? ```C++ // 遗留代码 #define WM_FUNC1 WM_USER + 1000 #define WM_FUNC2 WM_USER + 1001 class CFooWnd { public: void OnFunc1(WPARAM wParam, LPARAM lParam) { // Handler for WM_FUNC1 } void OnFunc2(WPARAM wParam, LPARAM lParam) { // Handler for WM_FUNC2 } // other functions and variables }; class CBar { public: void RegNotify(HWND hWnd) { m_hNotifyWnd = hWnd; } void TestFunc() { PostMessage(m_hNotifyWnd, WM_FUNC1, 1, 2); PostMessage(m_hNotifyWnd, WM_FUNC2, 3, 4); } private: HWND m_hNotifyWnd; }; void Init() { // 需要手动创建窗口线程,这里省略 CFooWnd *pWnd = new CFooWnd(); HWND hWnd = pWnd->Create(HWND_MESSAGE); CBar *pBar = GetBar(); pBar->RegNotify(hWnd); } ``` ```C++ // 改造后代码 class IFoo { public: // 可以是任意个数、任意类型的参数 virtual void OnFunc1(WPARAM wParam, LPARAM lParam) = 0; virtual Task<> OnFunc2(WPARAM wParam, LPARAM lParam) = 0; }; class CFoo : public IFoo { public: void OnFunc1(WPARAM wParam, LPARAM lParam) override { // 方式1 if (!IsInTaskThread(ATID_FOO)) { PostTask(ATID_FOO, &CFoo::OnFunc1, this, wParam, lParam); return; } // in the task thread } Task<> OnFunc2(WPARAM wParam, LPARAM lParam) override { // 方式2 co_await SwitchToTaskThread(ATID_FOO); // in the task thread } // other functions and variables }; class CBar { public: void RegNotify(std::shared_ptr pFoo) { m_pNotifyFoo = pFoo; } void TestFunc() { m_pNotifyFoo->OnFunc1(1, 2); m_pNotifyFoo->OnFunc2(3, 4); } private: std::shared_ptr m_pNotifyFoo; }; void Init() { // 不需要手动创建任何线程 std::shared_ptr pFoo = std::make_shared(); std::shared_ptr pBar = GetBar(); pBar->RegNotify(pFoo); } ```