在异步编程中,一个异步操作在完成时调用另一个操作并将数据传递到其中的情况非常常见。 传统上,这使用回调方法来完成。 在并发运行时中,延续任务提供了同样的功能。 延续任务(也简称为“延续”)是一个异步任务,由另一个任务(称为先行)在完成时调用。 使用延续可以:
这些功能使你可以在第一个任务完成时执行一个或多个任务。 例如,可以创建在第一个任务从磁盘读取文件之后压缩文件的延续。
下面的示例将上面的示例修改为使用 concurrency::task::then 方法来计划在先行任务的值可用时打印该值的延续。
// basic-continuation.cpp
// compile with: /EHsc
#include
#include
using namespace concurrency;
using namespace std;
int wmain()
{
auto t = create_task([]() -> int
{
return 42;
});
t.then([](int result)
{
wcout << result << endl;
}).wait();
// Alternatively, you can chain the tasks directly and
// eliminate the local variable.
/*create_task([]() -> int
{
return 42;
}).then([](int result)
{
wcout << result << endl;
}).wait();*/
}
/* Output:
42
*/
可以按任意长度链接和嵌套任务。 一个任务还可以具有多个延续。 下面的示例演示将上一个任务的值增加三倍的基本延续链。
// continuation-chain.cpp
// compile with: /EHsc
#include
#include
using namespace concurrency;
using namespace std;
int wmain()
{
auto t = create_task([]() -> int
{
return 0;
});
// Create a lambda that increments its input value.
auto increment = [](int n) { return n + 1; };
// Run a chain of continuations and print the result.
int result = t.then(increment).then(increment).then(increment).get();
wcout << result << endl;
}
/* Output:
3
*/
延续还可以返回另一个任务。 如果没有取消,则此任务会在后续延续之前执行。 此技术称为异步解包。 要在后台执行其他工作,但不想当前任务阻止当前线程时,异步解包会很有用。 (这在 UWP 应用中很常见,其中延续可以在 UI 线程上运行)。 下面的示例演示三个任务。 第一个任务返回在延续任务之前运行的另一个任务。
// async-unwrapping.cpp
// compile with: /EHsc
#include
#include
using namespace concurrency;
using namespace std;
int wmain()
{
auto t = create_task([]()
{
wcout << L"Task A" << endl;
// Create an inner task that runs before any continuation
// of the outer task.
return create_task([]()
{
wcout << L"Task B" << endl;
});
});
// Run and wait for a continuation of the outer task.
t.then([]()
{
wcout << L"Task C" << endl;
}).wait();
}
/* Output:
Task A
Task B
Task C
*/
当任务的延续返回 N 类型的嵌套任务时,生成的任务具有 N 类型(而不是 task
对于其返回类型是 T 的 task 对象,可以向其延续任务提供 T 或 task
when_all 函数生成在任务集完成之后完成的任务。 此函数返回 std::vector 对象,其中包含集中每个任务的结果。 下面的基本示例使用 when_all 创建一个表示三个其他任务完成的任务。
// join-tasks.cpp
// compile with: /EHsc
#include
#include
#include
using namespace concurrency;
using namespace std;
int wmain()
{
// Start multiple tasks.
array, 3> tasks =
{
create_task([] { wcout << L"Hello from taskA." << endl; }),
create_task([] { wcout << L"Hello from taskB." << endl; }),
create_task([] { wcout << L"Hello from taskC." << endl; })
};
auto joinTask = when_all(begin(tasks), end(tasks));
// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;
// Wait for the tasks to finish.
joinTask.wait();
}
/* Sample output:
Hello from the joining thread.
Hello from taskA.
Hello from taskC.
Hello from taskB.
*/
传递给 when_all 的任务必须统一。 换句话说,它们必须全部返回相同类型。
还可以使用 && 语法生成在任务集完成之后完成的任务,如下面的示例所示。
auto t = t1 && t2; // same as when_all
可将延续与 when_all 结合使用以在任务集完成之后执行操作,这十分常见。 下面的示例将上面的示例修改为打印各自生成 int 结果的三个任务的总和。
// Start multiple tasks.
array, 3> tasks =
{
create_task([]() -> int { return 88; }),
create_task([]() -> int { return 42; }),
create_task([]() -> int { return 99; })
};
auto joinTask = when_all(begin(tasks), end(tasks)).then([](vector results)
{
wcout << L"The sum is "
<< accumulate(begin(results), end(results), 0)
<< L'.' << endl;
});
// Print a message from the joining thread.
wcout << L"Hello from the joining thread." << endl;
// Wait for the tasks to finish.
joinTask.wait();
/* Output:
Hello from the joining thread.
The sum is 229.
*/
在此示例中,还可以指定 task
如果任务集中的任何任务取消或引发异常,则 when_all 会立即完成,不等待其余任务完成。 如果引发异常,则运行时会在你对 when_all 返回的任务对象调用 task::get 或 task::wait 时再次引发异常。 如果有多个任务引发,则运行时会选择其中之一。 因此,请确保在所有任务完成之后观察到所有异常;未经处理的任务异常会导致应用终止。
下面是可以用于确保程序观察到所有异常的实用工具函数。 对于处于提供的范围内的每个任务,observe_all_exceptions 会触发再次引发的任何异常,然后会吞并该异常。
// Observes all exceptions that occurred in all tasks in the given range.
template
void observe_all_exceptions(InIt first, InIt last)
{
std::for_each(first, last, [](concurrency::task t)
{
t.then([](concurrency::task previousTask)
{
try
{
previousTask.get();
}
// Although you could catch (...), this demonstrates how to catch specific exceptions. Your app
// might handle different exception types in different ways.
catch (Platform::Exception^)
{
// Swallow the exception.
}
catch (const std::exception&)
{
// Swallow the exception.
}
});
});
}
请考虑一个使用 C++ 和 XAML 并将文件集写入磁盘的 UWP 应用。 下面的示例演示如何使用 when_all 和 observe_all_exceptions 确保该程序观察到所有异常。
// Writes content to files in the provided storage folder.
// The first element in each pair is the file name. The second element holds the file contents.
task MainPage::WriteFilesAsync(StorageFolder^ folder, const vector>& fileContents)
{
// For each file, create a task chain that creates the file and then writes content to it. Then add the task chain to a vector of tasks.
vector> tasks;
for (auto fileContent : fileContents)
{
auto fileName = fileContent.first;
auto content = fileContent.second;
// Create the file. The CreationCollisionOption::FailIfExists flag specifies to fail if the file already exists.
tasks.emplace_back(create_task(folder->CreateFileAsync(fileName, CreationCollisionOption::FailIfExists)).then([content](StorageFile^ file)
{
// Write its contents.
return create_task(FileIO::WriteTextAsync(file, content));
}));
}
// When all tasks finish, create a continuation task that observes any exceptions that occurred.
return when_all(begin(tasks), end(tasks)).then([tasks](task previousTask)
{
task_status status = completed;
try
{
status = previousTask.wait();
}
catch (COMException^ e)
{
// We'll handle the specific errors below.
}
// TODO: If other exception types might happen, add catch handlers here.
// Ensure that we observe all exceptions.
observe_all_exceptions(begin(tasks), end(tasks));
// Cancel any continuations that occur after this task if any previous task was canceled.
// Although cancellation is not part of this example, we recommend this pattern for cases that do.
if (status == canceled)
{
cancel_current_task();
}
});
}
下面是这个例子的运行:
1. 在 MainPage.xaml 中,添加一个 Button 控件。
2. 在 MainPage.xaml.h 中,将这些前向声明添加到 MainPage 类声明的 private 节。
void Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
concurrency::task WriteFilesAsync(Windows::Storage::StorageFolder^ folder, const std::vector>& fileContents);
3. 在 MainPage.xaml.cpp 中,实现 Button_Click 事件处理程序。
// A button click handler that demonstrates the scenario.
void MainPage::Button_Click(Object^ sender, RoutedEventArgs^ e)
{
// In this example, the same file name is specified two times. WriteFilesAsync fails if one of the files already exists.
vector> fileContents;
fileContents.emplace_back(make_pair(ref new String(L"file1.txt"), ref new String(L"Contents of file 1")));
fileContents.emplace_back(make_pair(ref new String(L"file2.txt"), ref new String(L"Contents of file 2")));
fileContents.emplace_back(make_pair(ref new String(L"file1.txt"), ref new String(L"Contents of file 3")));
Button1->IsEnabled = false; // Disable the button during the operation.
WriteFilesAsync(ApplicationData::Current->TemporaryFolder, fileContents).then([this](task previousTask)
{
try
{
previousTask.get();
}
// Although cancellation is not part of this example, we recommend this pattern for cases that do.
catch (const task_canceled&)
{
// Your app might show a message to the user, or handle the error in some other way.
}
Button1->IsEnabled = true; // Enable the button.
});
}
4. 在 MainPage.xaml.cpp 中,实现 WriteFilesAsync,如示例所示。
when_all 是生成 task 作为其结果的的非阻止函数。 与 task::wait 不同,可以安全地在 UWP 应用中在 ASTA(应用程序 STA)线程上调用此函数。