最近一直在看js、python、lua等脚本语言的异步编程,今天脑子一热突然想看看C++能否也有支持异步的相关类库,上网一搜还真的有
microsoft官方文档https://msdn.microsoft.com/library/windows/apps/Hh780559.aspx
主要使用task class 及其相关类型和函数,它们都包含在 concurrency 命名空间中且在
1.将多个异步和同步操作结合在一起
2.在任务链中处理异常
3.在任务链中执行取消
4.确保单个任务在相应的线程上下文或单元中运行
简单的一个小demo:
#include "stdafx.h"
#include
#include
#include
#include
#include
#include
using namespace Concurrency;
using namespace std;
int main()
{
array, 3> tasks = {
task([] { Sleep(3000); cout << "the f1 output\n"; Sleep(10000); return 100; }),
task([] { Sleep(1000); cout << "the f2 output\n" ; Sleep(10000); return 10; }),
task([] { Sleep(2000); cout << "the f3 output\n" ; Sleep(10000); return 1; })
};
//when_all().wait():容器里面所有的task都被执行后,才继续向下执行。
//when_any().wait():容器里第一个task完成之后,就继续向下执行。
auto TaskList = when_all(tasks.begin(), tasks.end())
.then([](vector results)
{
cout << "The sum is "
<< accumulate(results.begin(), results.end(), 0)
<< endl;
});
time_t t1 = time(NULL);
cout << "begin\n";
TaskList.wait();
cout << "end\n";
time_t t2 = time(NULL);
cout << "the runtime length " << t2 - t1 << "s " << endl;
system("pause");
}
我们定义了三个task,且每个return前都又10s的sleep来代表io操作,其结果总耗时为最长执行的f1(sleep(3000)+sleep(10000))。
官网文档相关说明:
通过任务使用异步操作
以下示例说明如何利用任务类来使用返回 IAsyncOperation 接口且其操作会生成一个值的async方法。以下是基本步骤:
调用 create_task 方法并将其传递到 IAsyncOperation^ 对象。
调用任务上的成员函数 task::then 并且提供一个将在异步操作完成时调用的 lambda。
#include
using namespace concurrency;
using namespace Windows::Devices::Enumeration;
...
void App::TestAsync()
{
//Call the *Async method that starts the operation.
IAsyncOperation^ deviceOp =
DeviceInformation::FindAllAsync();
// Explicit construction. (Not recommended)
// Pass the IAsyncOperation to a task constructor.
// task deviceEnumTask(deviceOp);
// Recommended:
auto deviceEnumTask = create_task(deviceOp);
// Call the task’s .then member function, and provide
// the lambda to be invoked when the async operation completes.
deviceEnumTask.then( [this] (DeviceInformationCollection^ devices )
{
for(int i = 0; i < devices->Size; i++)
{
DeviceInformation^ di = devices->GetAt(i);
// Do something with di...
}
}); // end lambda
// Continue doing work or return...
}
创建任务链
在异步编程中,常见的做法是定义一个操作序列,也称作任务链,其中每个延续只有在前一个延续完成后才能执行。在某些情况下,上一个(或先行)任务会产生一个被延续接受为输入的值。通过使用 task::then 方法,你可以按照直观且简单的方式创建任务链;该方法返回一个 task
当延续创建一个新的异步操作时,任务链尤其有用;此类任务称为异步任务。以下示例将介绍具有两个延续的任务链。初始任务获取一个现有文件的句柄,当该操作完成后,第一个延续会启动一个新的异步操作来删除该文件。当该操作完成后,第二个延续将运行,并且输出一条确认消息。
#include
using namespace concurrency;
...
void App::DeleteWithTasks(String^ fileName)
{
using namespace Windows::Storage;
StorageFolder^ localFolder = ApplicationData::Current::LocalFolder;
auto getFileTask = create_task(localFolder->GetFileAsync(fileName));
getFileTask.then([](StorageFile^ storageFileSample) ->IAsyncAction^ {
return storageFileSample->DeleteAsync();
}).then([](void) {
OutputDebugString(L"File deleted.");
});
}
注意 创建任务链只是使用 task 类组合异步操作的一种方式。还可以通过使用连接和选择运算符 && 和 || 来组合操作。有关更多信息,请参阅任务并行度(并发运行时)。
Lambda 函数返回类型和任务返回类型
在任务延续中,lambda 函数的返回类型包含在 task 对象中。如果该 lambda 返回 double,则延续任务的类型为 task
请注意,在上一个示例中,即使其 lambda 返回 IAsyncInfo 对象,该任务也仍然会返回 task
取消任务
为用户提供取消异步操作的选项通常是一个不错的方法。另外,在某些情况下,你可能必须以编程方式从任务链外部取消操作。尽管每个 *Async 返回类型都具有一个从 IAsyncInfo 继承的 Cancel 方法,但将其公开给外部方法是不可取的。在任务链中支持取消的首选方式是使用 cancellation_token_source 创建 cancellation_token,然后将该令牌传递给初始任务的构造函数。如果使用取消令牌创建异步任务,并且调用 cancellation_token_source::cancel,则该任务会自动对 IAsync* 操作调用 Cancel,并且将取消请求沿着其延续链向下传递。下面的伪代码演示了基本方法。
//Class member:
cancellation_token_source m_fileTaskTokenSource;
// Cancel button event handler:
m_fileTaskTokenSource.cancel();
// task chain
auto getFileTask2 = create_task(documentsFolder->GetFileAsync(fileName),
m_fileTaskTokenSource.get_token());
//getFileTask2.then ...
在任务链中处理错误
如果希望让一个延续即使在先行被取消或引发异常的情况下也能够执行,请将该延续的 lambda 函数的输入指定为 task
若要在任务链中处理错误和取消,则无需使每个延续成为基于任务的延续,也不用将每个可能引发异常的操作封装在 try…catch 块中。相反,你可以将基于任务的延续添加至任务链的末尾,并且在那里处理所有错误。任何异常(包括 task_canceled 异常)都将沿着任务链向下传播并绕过所有基于值的延续,因此你可以在基于任务的错误处理延续中处理这些异常。我们可以重写上一个示例以使用基于任务的错误处理延续:
#include
void App::DeleteWithTasksHandleErrors(String^ fileName)
{
using namespace Windows::Storage;
using namespace concurrency;
StorageFolder^ documentsFolder = KnownFolders::DocumentsLibrary;
auto getFileTask = create_task(documentsFolder->GetFileAsync(fileName));
getFileTask.then([](StorageFile^ storageFileSample)
{
return storageFileSample->DeleteAsync();
})
.then([](task t)
{
try
{
t.get();
// .get() didn't throw, so we succeeded.
OutputDebugString(L"File deleted.");
}
catch (Platform::COMException^ e)
{
//Example output: The system cannot find the specified file.
OutputDebugString(e->Message->Data());
}
});
}
管理线程上下文
Windows 运行时应用的 UI 在单线程单元 (STA) 中运行。其 lambda 返回 IAsyncAction 或 IAsyncOperation 的任务具有单元意识。如果该任务是在 STA 中创建的,则默认情况下,除非你另外指定,否则该任务的所有要运行的延续也将在该 STA 中运行。 换句话说,整个任务链从父任务继承单元意识。该行为可帮助简化与 UI 控件的交互,这些 UI 控件只能从 STA 访问。
例如,在 Windows 运行时应用中,在任何表示 XAML 页面的类的成员函数中,你可以从 task::then 方法内部填充 ListBox 控件,而无需使用 Dispatcher 对象。
#include
void App::SetFeedText()
{
using namespace Windows::Web::Syndication;
using namespace concurrency;
String^ url = "http://windowsteamblog.com/windows_phone/b/wmdev/atom.aspx";
SyndicationClient^ client = ref new SyndicationClient();
auto feedOp = client->RetrieveFeedAsync(ref new Uri(url));
create_task(feedOp).then([this] (SyndicationFeed^ feed)
{
m_TextBlock1->Text = feed->Title->Text;
});
}
如果任务不返回 IAsyncAction 或 IAsyncOperation,则它不具有单元意识,并且默认情况下,其延续在第一个可用的后台线程上运行。
你可以使用 task_continuation_context 的接受 task::then 的重载来覆盖任一种任务的默认线程上下文。例如,在某些情况下,在后台线程上计划具有单元意识的任务的延续或许是可取的。在此情况下,你可以传递 task_continuation_context::use_arbitrary 以便在多线程单元中的下一个可用线程上计划该任务的工作。这可以改善延续的性能,因为其工作不必与 UI 线程上发生的其他工作同步。
以下示例将演示何时指定 task_continuation_context::use_arbitrary 选项是有用的,还说明了默认延续上下文在同步非线程安全集合上的并发操作方面是多么有用。在此段代码中,我们遍历 RSS 源的 URL 列表,并为每个 URL 启动一个异步操作以检索源数据。 我们无法控制检索订阅的顺序,而我们其实并不关心。当每个 RetrieveFeedAsync 操作完成后,第一个延续接受 SyndicationFeed^ 对象并使用它来初始化应用定义的 FeedData^ 对象。因为上述每个操作都独立于其他操作,所以我们可以通过指定 task_continuation_context::use_arbitrary 延续上下文来提高运行速度。 但是,在初始化每个 FeedData 对象之后,我们必须将其添加至一个不是线程安全集合的 Vector 中。因此,我们要创建一个延续并且指定 task_continuation_context::use_current 以确保所有对 Append 的调用都发生在同样的应用程序单线程单元 (ASTA) 上下文中。由于 task_continuation_context::use_default 是默认上下文,因此我们无需进行明确指定,但是此处为了清楚起见而予以指定。
#include
void App::InitDataSource(Vector
处理进度更新
在操作完成之前,支持 IAsyncOperationWithProgress 或 IAsyncActionWithProgress 的方法会在操作执行过程中定期提供进度更新。进度报告独立于任务和延续概念。 你只需为对象的 Progress 属性提供委派。委派的一个典型用途是更新 UI 中的进度栏。