C++线程异步和wpf中比较

概念理解

  • C++
    • 线程
    • 异步任务
    • [协程 (C++20 Coroutines) ------ "真正的 async/await"](#协程 (C++20 Coroutines) —— “真正的 async/await”)
    • [回到 WPF 的 await](#回到 WPF 的 await)

C++

在使用wpf中发现一个很方便的使用,我在下发任务时,直接使用async 标记一个异步方法,在这个异步方法里面使用await去执行一个比较耗时的方法,完全不卡界面,而且该耗时方法在执行完成切到当前函数,然后继续进行,觉得使用起来很简单,和C++进行比对如下,深入理解一下相关概念

线程

  1. 真·并行:它真的在多核 CPU 的另一个核心上跑。
  2. 手动管理:你必须决定是 join()(主线程停下来等它结束)还是 detach()(让它自己跑,主线程不管了,类似"后台守护线程")。
  3. 无返回值:函数跑完了,结果怎么拿?你得自己通过引用参数、全局变量或者配合 std::promise 来传值。
  4. 风险大:如果主线程结束了而子线程没 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之后可以等那边执行完成之后自动返回,执行后续流程,真正的异步

相关推荐
umeelove352 小时前
Springboot的jak安装与配置教程
java·spring boot·后端
The_Ticker2 小时前
日股实时行情接口使用指南
java·经验分享·笔记·python·算法·区块链
啥咕啦呛2 小时前
java打卡学习2:Stream高级与Optional
java·windows·学习
试试勇气2 小时前
Linux学习笔记(十九)--生产消费模型与线程安全
java·笔记·学习
凌波粒2 小时前
LeetCode--24.两两交换链表中的节点(链表)
java·算法·leetcode·链表
pupudawang2 小时前
Spring Boot 各种事务操作实战(自动回滚、手动回滚、部分回滚)
java·数据库·spring boot
C++chaofan2 小时前
RPC框架SPI机制深度解析
java·网络·后端·网络协议·rpc·spi·序列化器
艾莉丝努力练剑2 小时前
【Linux信号】Linux进程信号(中):信号保存、信号处理(含“OS是如何运行的?”)
大数据·linux·运维·服务器·数据库·c++·mysql
名字忘了取了2 小时前
线程池-submit 与 execute
java