C++ std::async 工具使用

std::async 是 C++11 引入的一个工具函数,它主要用于在程序中创建 异步任务延迟任务。本篇文章将通过设计的 6 个示例程序来展示如何利用 std::async 实现异步任务与延迟任务。

1. 异步任务

一个任务或操作可以独立于主线程(或主任务)运行,主线程无需等待任务完成便可继续执行其他工作。当异步任务完成时,可以通过某种机制(如 std::future)获取其结果。

1.1 异步任务的基本使用

这一小节主要介绍如何创建异步任务,以及如何获得异步任务的返回结果。这里需要注意的是:std::async 函数的第一个参数如果没有指定的话,那么默认任务是异步任务还是延迟任务取决于具体实现。

#if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<thread>
#include<chrono>
#include<future>


int task(int number)
{
	std::this_thread::sleep_for(std::chrono::seconds(2));
	return 100 + number;
}


void test()
{
	// 下面代码执行完成之后,异步任务就开始执行
	std::future<int> fut = std::async(std::launch::async, task, 10);
	std::cout << "任务开始执行..." << std::endl;

	try
	{
		// 阻塞等待任务函数执行结束,并得到结果
		int ret = fut.get();
		std::cout << "ret = " << ret << std::endl;
	}
	catch (const std::exception& ex)
	{
		std::cout << ex.what() << std::endl;
	}

	std::cout << "程序结束" << std::endl;
}


int main()
{
	test();
	return EXIT_SUCCESS;
}
#endif

1.2 没有返回值任务场景

wait 函数会阻塞等待任务函数执行结束(正常结束、异常结束),并不关心返回值。注意:如果任务函数抛出异常,wait 函数不会再抛出,而是保存到 std::future 对象内部,当调用 get 函数时会抛出异常。

案例:A B 两个异步任务分别执行将不同的内容写入到不同的文件中,然后由主线程将两个文件的内容合并到第三个文件中。

#if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<thread>
#include<chrono>
#include<future>
#include<fstream>


void write_file(std::string filename, std::string content)
{
	std::ofstream ofs(filename);

	// 等待 2 秒,表示耗时任务
	std::this_thread::sleep_for(std::chrono::seconds(2));

	ofs << content << std::endl;
	ofs.close();
}


void test()
{
	// 下面代码执行完成之后,线程函数就开始执行
	std::future<void> fut1 = std::async(std::launch::async, write_file, "demo1.txt", "hello world");
	std::future<void> fut2 = std::async(std::launch::async, write_file, "demo2.txt", "hello python");
	std::cout << "任务开始执行..." << std::endl;

	// 等待异步任务执行完毕
	fut1.wait();
	fut2.wait();

	// 合并文件
	std::ifstream ifs1("demo1.txt");
	std::ifstream ifs2("demo2.txt");
	std::ofstream ofs("demo.txt");

	std::copy(std::istreambuf_iterator<char>(ifs1), std::istreambuf_iterator<char>(), std::ostreambuf_iterator<char>(ofs));
	std::copy(std::istreambuf_iterator<char>(ifs2), std::istreambuf_iterator<char>(), std::ostreambuf_iterator<char>(ofs));

	ifs1.close();
	ifs2.close();
	ofs.close();

	std::cout << "程序结束" << std::endl;
}


int main()
{
	test();
	return EXIT_SUCCESS;
}
#endif

1.3 非阻塞获得任务结果

get 和 wait 都是阻塞等待任务结束。也可以通过 wait_for、wait_until 来非阻塞方式等待异步任务结果。

  • wait_for 会在指定时间内等待任务状态变化,如果任务没有结束,返回 timeout,否则返回 ready
  • wait_until 则表示等待到某个时间点,如果任务状态没有发生变化,则返回 timeout,否则返回 ready

我们可以通过不停的检查任务的状态,来实现非阻塞的方式获得任务结果。

#if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<thread>
#include<chrono>
#include<future>


int calculate(int number)
{
	int result = 0;
	for (int i = 0; i < number; ++i)
	{
		std::this_thread::sleep_for(std::chrono::seconds(1));
		result += 10;
	}

	return result;
}


void test()
{
	std::future<int> fut = std::async(std::launch::async, calculate, 5);
	std::cout << "任务开始执行..." << std::endl;


	while (true)
	{
		std::future_status status = fut.wait_for(std::chrono::seconds(1));
		// std::future_status status = fut.wait_until(std::chrono::steady_clock().now() + std::chrono::seconds(2));
		if (status == std::future_status::ready)
		{
			std::cout << "子线程结果:" << fut.get() << std::endl;
			break;
		}

		if (status == std::future_status::timeout)
		{
			std::cout << "子任务尚未执行完毕,做会其他的事情..." << std::endl;
		}
	}
}


int main()
{
	test();
	return EXIT_SUCCESS;
}
#endif

2. 延迟任务

将任务的执行推迟,直到确实需要时才开始执行。这种方式通常用于系统负载较高时,任务的结果又不立即需要的场景,帮助系统优化资源使用,提升整体性能。

2.1 延迟任务的基本使用

这一小结主要通过两个案例来介绍如何创建延迟任务、如何触发延迟任务执行、以及如何通过阻塞和非阻塞两种方式获得任务结果。

#if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<thread>
#include<chrono>
#include<future>


int task(int number)
{
	std::this_thread::sleep_for(std::chrono::seconds(8));
	return 100 + number;
}

void test01()
{
	// 任务不会马上执行
	std::future<int> fut = std::async(std::launch::deferred, task, 10);

	// 当调用 get、wait 函数时,触发任务函数执行
	int ret = fut.get();
	// std::cout << "ret = " << ret << std::endl;
	// fut.wait();
}


void test02()
{
	std::future<int> fut = std::async(std::launch::deferred, task, 10);
	bool is_start = false;

	while (true)
	{
		// 当延迟任务尚未执行前,wait_for 函数不会阻塞,会立即返回
		std::future_status status = fut.wait_for(std::chrono::seconds(1));

		if (status == std::future_status::deferred && !is_start)
		{
			std::cout << "任务尚未开始执行..." << std::endl;
			std::thread([&fut]() { fut.wait(); }).detach();
			is_start = true;
		}

		if (status == std::future_status::timeout)
		{
			std::cout << "任务正在执行..." << std::endl;
		}

		if (status == std::future_status::ready)
		{
			std::cout << "任务执行完毕,执行结果是:" << fut.get() << std::endl;
			break;
		}
	}
}


int main()
{
	test02();
	return EXIT_SUCCESS;
}
#endif

2.2 独享和共享任务结果

我们一直使用 std::future 来获得任务的结果,但是它适合一次性获取结果,多次获取会导致异常。当任务需要在多个地方、或者多次获取结果时,需要使用 std::shared_future 来代替 std::future

  • 我们可以在调用 std::async 函数时,直接使用 std::shared_future 来承接返回值。
  • 也可以调用 std::futureshare 函数来返回一个 std::shared_future 类型的对象。

#if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<thread>
#include<chrono>
#include<future>


void test()
{
	std::future<int> fut = std::async(std::launch::deferred, []() {
		std::cout << "延迟任务开始执行..." << std::endl;
		std::this_thread::sleep_for(std::chrono::seconds(2));
		return 100;
	});

#if 0
	// std::future 只能获取一次结果,重复获取结果将会抛出异常
	// std::shared_future 可以重复获取结果
	int ret = fut.get();
	ret = fut.get();
#else
	std::shared_future<int> shared_fut = fut.share();
	int ret = shared_fut.get();
	ret = shared_fut.get();
#endif
}


int main()
{
	test();
	return EXIT_SUCCESS;
}
#endif

2.3 延迟任务的案例理解

假设:用户在网页查看某张图片,但是只有处理过的图片才能够展示给用户。我们可以:

  1. 集中时间处理所有图片,这样的话,会在一段时间时间内占用大量的系统资源,会影响系统性能
  2. 按需处理图片,用户请求那张就开个线程处理哪张,处理过的图像直接返回,剩余未处理的可以在系统空闲时再集中处理

显然,在系统资源有限的情况下,第二种更合适一些。第二种就需要用到延时任务。

#if 1
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<thread>
#include<chrono>
#include<future>
#include<map>


std::string data_analysis(std::string path)
{
	std::this_thread::sleep_for(std::chrono::seconds(10));
	return path;
}


void test()
{

	std::map<std::string, std::tuple<bool, std::shared_future<std::string>>> tasks;

	// 添加延迟图像处理任务
	tasks.insert({ "a.png", {false, std::async(std::launch::deferred, data_analysis, "a.png")} });
	tasks.insert({ "b.png", {false, std::async(std::launch::deferred, data_analysis, "b.png")} });
	tasks.insert({ "c.png", {false, std::async(std::launch::deferred, data_analysis, "c.png")} });

	// 用户查看图像
	std::string name;
	while (true)
	{
		std::cout << "请输入查看的图像:";
		std::cin >> name;

		if (name == "exit")
		{
			for (auto& task : tasks)
			{
				std::cout << task.first << " " << std::boolalpha << std::get<0>(task.second) << std::endl;
			}
			std::cout << "----------------" << std::endl;
			break;
		}

		// 查看图像目前状态
		auto& picture = tasks[name];
		auto& is_ok = std::get<0>(picture);
		auto& fut = std::get<1>(picture);

		if (is_ok)
		{
			std::cout << "您要查看的图像是:" << fut.get() << std::endl;
			continue;
		}

		std::future_status status = fut.wait_for(std::chrono::seconds(1));

		if (status == std::future_status::deferred)
		{
			std::cout << "图像尚未处理, 开始处理图像..." << std::endl;
			std::thread([&fut]() { fut.wait(); }).detach();
		}

		if (status == std::future_status::timeout)
		{
			std::cout << "图像正在处理中" << std::endl;
		}

		if (status == std::future_status::ready)
		{
			is_ok = true;
		}

		std::cout << "----------------" << std::endl;
	}
}


int main()
{
	test();
	return EXIT_SUCCESS;
}
#endif

未经允许不得转载:一亩三分地 » C++ std::async 工具使用
评论 (1)

8 + 3 =

  1. avatar
    utopio12-25 23:01回复

    支持一下。