概念理解
C++
在使用wpf中发现一个很方便的使用,我在下发任务时,直接使用async 标记一个异步方法,在这个异步方法里面使用await去执行一个比较耗时的方法,完全不卡界面,而且该耗时方法在执行完成切到当前函数,然后继续进行,觉得使用起来很简单,和C++进行比对如下,深入理解一下相关概念
线程
- 真·并行:它真的在多核 CPU 的另一个核心上跑。
- 手动管理:你必须决定是 join()(主线程停下来等它结束)还是 detach()(让它自己跑,主线程不管了,类似"后台守护线程")。
- 无返回值:函数跑完了,结果怎么拿?你得自己通过引用参数、全局变量或者配合 std::promise 来传值。
- 风险大:如果主线程结束了而子线程没 join 也没 detach,程序会直接崩溃 (std::terminate)。
cpp
#include <iostream>
#include <thread>
#include <chrono>
void heavyTask() {
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "子线程:任务完成!" << std::endl;
}
int main() {
std::cout << "主线程:开始" << std::endl;
// 1. 创建并启动线程
std::thread t(heavyTask);
// 2. 必须决定命运:
// t.join(); // 主线程在这里阻塞等待,直到 t 跑完(同步效果)
t.detach(); // 主线程继续跑,t 在后台自己跑(异步效果,但无法获取返回值)
std::cout << "主线程:继续做别的事..." << std::endl;
// 注意:如果 main 结束,detach 的线程可能会被强制杀死,需小心处理生命周期
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
异步任务
C++11 引入了更高级的抽象。你不再直接管理线程,而是提交一个"任务",系统决定是开新线程跑还是稍微晚点跑。
含义:
std::async:启动一个异步任务,返回一个 std::future。
std::future:一个"未来对象",代表还没出来的结果。你可以用它来等待结果或获取结果。
特点:
自动管理:不需要手动 join,future 析构时会自动等待(除非你特意分离)。
有返回值:直接 f.get() 就能拿到函数返回值。
线程池策略:默认情况下(std::launch::async | std::launch::deferred),标准库实现可能会复用线程池,不一定每次都新开线程(取决于具体编译器实现)
cpp
#include <iostream>
#include <future>
#include <chrono>
#include <thread>
int heavyTask() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 42; // 返回结果
}
int main() {
std::cout << "主线程:提交任务" << std::endl;
// 1. 启动异步任务 (std::launch::async 强制新开线程)
std::future<int> result = std::async(std::launch::async, heavyTask);
std::cout << "主线程:任务已提交,正在做别的事..." << std::endl;
// ... 做一些其他事 ...
// 2. 获取结果 (如果任务没做完,这里会阻塞等待)
std::cout << "主线程:等待结果..." << std::endl;
int value = result.get(); // 拿到返回值 42
std::cout << "主线程:结果是 " << value << std::endl;
return 0;
}
类比:你把任务包给快递公司(std::async),给你一个单号(std::future)。你可以先去忙别的,想查结果时凭单号去取(.get()),如果货没到,你就得在柜台等着。
std::async和std::future启动是异步的,但获取结果是同步的(阻塞的)
最大的意义在于把"等待的时间"利用起来!
cpp
// 1. 提交任务(后台开始做菜)
auto result = std::async(heavyTask);
// 2. 【关键】在等待菜做好的这 2 秒里,主线程可以去干别的事!
doSomethingElse(); // 假设这也需要 1 秒。此时后台也在做菜。
// 3. 等到需要做结果的时候,再去取
// 如果 doSomethingElse() 花了 1 秒,而做菜要 2 秒。
// 此时调用 get(),只需要再等 1 秒 (2-1=1)。
// 如果 doSomethingElse() 花了 3 秒,此时菜早好了,get() 瞬间返回,不用等。
int value = result.get();
总耗时:Max(2 秒做菜,1 秒做别的) + 剩余等待 = 约 2 秒多一点。
体验:界面流畅,因为耗时的 doSomethingElse 和后台计算重叠执行了。
协程 (C++20 Coroutines) ------ "真正的 async/await"
这是 C++20 引入的革命性特性,终于有了类似 C# 的 co_await。
含义:函数可以暂停和恢复,而不阻塞线程。
巨大差异(重要!):
C# 的 async/await:是开箱即用的。编译器帮你生成状态机,标准库提供了 Task,你直接用就行。
C++20 的 co_await:只是底层机制(语法关键字)。标准库没有提供现成的 Task 类或调度器!
这意味着:你想在 C++20 里用协程,通常需要自己写一个 Task 类,或者使用第三方库(如 cppcoro, Boost.ASIO, libunifex)。这就像给了你发动机零件,但没给你整车,你得自己组装(或者找别人组装好的车)。
cpp
// 伪代码示例 (需要配合 cppcoro 等库才能编译运行)
#include <cppcoro/task.hpp>
#include <cppcoro/sleep.hpp>
// 这是一个协程函数
task<int> myAsyncFunction() {
auto result = std::async(heavyTask);
// co_await 的神奇之处:
// 如果任务没好,它会【挂起】当前函数,把控制权交还给调用者(比如 UI 消息循环)。
// 它不会阻塞线程!
// 等任务好了,系统会自动【恢复】这个函数,继续往下执行。
int value = co_await result;
updateUI(value); // 这里拿到值时,界面一直是流畅的
}
回到 WPF 的 await
cpp
/// <summary>
/// 模拟耗时操作,并返回一个整数结果
/// </summary>
/// <param name="milliseconds">等待时间</param>
/// <returns>等待结束后的计数值</returns>
private async Task<int> GetSnapshotAfterDelayAsync(int milliseconds)
{
// 1. 先异步等待 (不阻塞界面)
await Task.Delay(milliseconds);
// 2. 等待结束后,执行逻辑 (比如加锁读取数据)
int resultValue;
lock (_lockObj)
{
resultValue = _sharedCounter;
}
// 3. 返回结果
// 注意:虽然返回类型是 Task<int>,但你这里直接 return int 即可
// 编译器会自动把它包装成 Task<int>
return resultValue;
}
private async void BtnCheck_Click(object sender, RoutedEventArgs e)
{
btnCheck.IsEnabled = false;
lblStatus.Text = "状态:正在执行带返回值的异步任务...";
lblSnapshot.Text = "正在等待并计算...";
// 👇 关键点:await 之后,得到的直接就是 int 类型的值
// 就像调用普通同步方法一样自然
int finalValue = await GetSnapshotAfterDelayAsync(2000);
// 代码执行到这里,说明 2 秒已过,且拿到了返回值
lblSnapshot.Text = $"异步读取结果: {finalValue}";
lblStatus.Text = $"状态:任务完成。最终返回值是 {finalValue}";
btnCheck.IsEnabled = true;
}
这里和协程很像,都是任务丢出去,切回调用者线程,等任务执行完成继续往后执行后续代码, 而在20以前
我如果需要拿值 int value = result.get(); 这时候都是阻塞的。
而20之后可以等那边执行完成之后自动返回,执行后续流程,真正的异步