淘寶客網(wǎng)站還可以做嗎牛奶軟文廣告營銷
延續(xù)任務(wù)
在異步編程中,一個異步操作在完成時調(diào)用另一個操作并將數(shù)據(jù)傳遞到其中的情況非常常見。 傳統(tǒng)上,這使用回調(diào)方法來完成。 在并發(fā)運行時中,延續(xù)任務(wù)提供了同樣的功能。 延續(xù)任務(wù)(也簡稱為“延續(xù)”)是一個異步任務(wù),由另一個任務(wù)(稱為先行)在完成時調(diào)用。 使用延續(xù)可以:
- 將數(shù)據(jù)從前面的任務(wù)傳遞到延續(xù);
- 指定調(diào)用或不調(diào)用延續(xù)所依據(jù)的精確條件;
- 在延續(xù)啟動之前取消延續(xù),或在延續(xù)正在運行時以協(xié)作方式取消延續(xù);
- 提供有關(guān)應(yīng)如何計劃延續(xù)的提示。 (這僅適用于通用 Windows 平臺 (UWP) 應(yīng)用;
- 從同一前面的任務(wù)中調(diào)用多個延續(xù);
- 在多個先行任務(wù)中的全部或任意任務(wù)完成時調(diào)用一個延續(xù);
- 將延續(xù)依次相連,形成任意長度;
- 使用延續(xù)來處理先行引發(fā)的異常;
這些功能使你可以在第一個任務(wù)完成時執(zhí)行一個或多個任務(wù)。 例如,可以創(chuàng)建在第一個任務(wù)從磁盤讀取文件之后壓縮文件的延續(xù)。
下面的示例將上面的示例修改為使用 concurrency::task::then 方法來計劃在先行任務(wù)的值可用時打印該值的延續(xù)。
// basic-continuation.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>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
*/
可以按任意長度鏈接和嵌套任務(wù)。 一個任務(wù)還可以具有多個延續(xù)。 下面的示例演示將上一個任務(wù)的值增加三倍的基本延續(xù)鏈。
// continuation-chain.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>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
*/
延續(xù)還可以返回另一個任務(wù)。 如果沒有取消,則此任務(wù)會在后續(xù)延續(xù)之前執(zhí)行。 此技術(shù)稱為異步解包。 要在后臺執(zhí)行其他工作,但不想當前任務(wù)阻止當前線程時,異步解包會很有用。 (這在 UWP 應(yīng)用中很常見,其中延續(xù)可以在 UI 線程上運行)。 下面的示例演示三個任務(wù)。 第一個任務(wù)返回在延續(xù)任務(wù)之前運行的另一個任務(wù)。?
// async-unwrapping.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <iostream>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 ATask BTask C
*/
當任務(wù)的延續(xù)返回 N 類型的嵌套任務(wù)時,生成的任務(wù)具有 N 類型(而不是 task<N>),會在嵌套任務(wù)完成時完成。 換句話說,延續(xù)會執(zhí)行嵌套任務(wù)的解包。?
基于值的延續(xù)與基于任務(wù)的延續(xù)
對于其返回類型是 T 的 task 對象,可以向其延續(xù)任務(wù)提供 T 或 task<T> 類型的值。 采用類型 T 的延續(xù)稱為基于值的延續(xù)。 基于值的延續(xù)計劃在先行任務(wù)完成而未出現(xiàn)錯誤并且未取消時執(zhí)行。 采用類型 task<T> 作為其參數(shù)的延續(xù)稱為基于任務(wù)的延續(xù)。 基于任務(wù)的延續(xù)始終計劃為在先行任務(wù)完成時執(zhí)行,甚至是在先行任務(wù)取消或引發(fā)異常時執(zhí)行。 隨后然后調(diào)用 task::get 以獲取先行任務(wù)的結(jié)果。 如果先行任務(wù)已取消,則 task::get 會引發(fā) concurrency::task_canceled。 如果先行任務(wù)引發(fā)了異常,則 task::get 會再次引發(fā)該異常。 基于任務(wù)的延續(xù)在先行任務(wù)取消時不會標記為已取消。
when_all 函數(shù)
when_all 函數(shù)生成在任務(wù)集完成之后完成的任務(wù)。 此函數(shù)返回 std::vector 對象,其中包含集中每個任務(wù)的結(jié)果。 下面的基本示例使用 when_all 創(chuàng)建一個表示三個其他任務(wù)完成的任務(wù)。
// join-tasks.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>using namespace concurrency;
using namespace std;int wmain()
{// Start multiple tasks.array<task<void>, 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 的任務(wù)必須統(tǒng)一。 換句話說,它們必須全部返回相同類型。
還可以使用 && 語法生成在任務(wù)集完成之后完成的任務(wù),如下面的示例所示。
auto t = t1 && t2; // same as when_all
可將延續(xù)與 when_all 結(jié)合使用以在任務(wù)集完成之后執(zhí)行操作,這十分常見。 下面的示例將上面的示例修改為打印各自生成 int 結(jié)果的三個任務(wù)的總和。
// Start multiple tasks.
array<task<int>, 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<int> 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<vector<int>> 以生成基于任務(wù)的延續(xù)。
如果任務(wù)集中的任何任務(wù)取消或引發(fā)異常,則 when_all 會立即完成,不等待其余任務(wù)完成。 如果引發(fā)異常,則運行時會在你對 when_all 返回的任務(wù)對象調(diào)用 task::get 或 task::wait 時再次引發(fā)異常。 如果有多個任務(wù)引發(fā),則運行時會選擇其中之一。 因此,請確保在所有任務(wù)完成之后觀察到所有異常;未經(jīng)處理的任務(wù)異常會導致應(yīng)用終止。
下面是可以用于確保程序觀察到所有異常的實用工具函數(shù)。 對于處于提供的范圍內(nèi)的每個任務(wù),observe_all_exceptions 會觸發(fā)再次引發(fā)的任何異常,然后會吞并該異常。
// Observes all exceptions that occurred in all tasks in the given range.
template<class T, class InIt>
void observe_all_exceptions(InIt first, InIt last)
{std::for_each(first, last, [](concurrency::task<T> t){t.then([](concurrency::task<T> 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 應(yīng)用。 下面的示例演示如何使用 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<void> MainPage::WriteFilesAsync(StorageFolder^ folder, const vector<pair<String^, String^>>& 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<task<void>> 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<void> 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<void>(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 控件。<Button x:Name="Button1" Click="Button_Click">Write files</Button>2. 在 MainPage.xaml.h 中,將這些前向聲明添加到 MainPage 類聲明的 private 節(jié)。void Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);
concurrency::task<void> WriteFilesAsync(Windows::Storage::StorageFolder^ folder, const std::vector<std::pair<Platform::String^, Platform::String^>>& fileContents);3. 在 MainPage.xaml.cpp 中,實現(xiàn) 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<pair<String^, String^>> 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<void> 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 中,實現(xiàn) WriteFilesAsync,如示例所示。
when_all 是生成 task 作為其結(jié)果的的非阻止函數(shù)。 與 task::wait 不同,可以安全地在 UWP 應(yīng)用中在 ASTA(應(yīng)用程序 STA)線程上調(diào)用此函數(shù)。?