Асинхронное программирование на языке C++/CXAsynchronous programming in C++/CX

Примечание

В этом разделе представлена вспомогательная информация для поддержки приложений на C++/CX.This topic exists to help you maintain your C++/CX application. Однако в новых приложениях мы рекомендуем использовать C++/WinRT.But we recommend that you use C++/WinRT for new applications. C++/WinRT — это полностью стандартная проекция языка C++17 для API среды выполнения Windows (WinRT), реализованная как библиотека на основе файлов заголовков и предназначенная для предоставления вам первоклассного доступа к современным интерфейсам API Windows.C++/WinRT is an entirely standard modern C++17 language projection for Windows Runtime (WinRT) APIs, implemented as a header-file-based library, and designed to provide you with first-class access to the modern Windows API.

В этой статье описываются рекомендации по использованию асинхронных методов в расширениях компонентов Visual C++ (C++/CX) с помощью класса task, определенного в пространстве имен concurrency файла ppltasks.h.This article describes the recommended way to consume asynchronous methods in Visual C++ component extensions (C++/CX) by using the task class that's defined in the concurrency namespace in ppltasks.h.

Асинхронные типы универсальной платформы для Windows (UWP)Universal Windows Platform (UWP) asynchronous types

Универсальная платформа Windows (UWP) имеет четкую модель вызова асинхронных методов и предоставляет типы для использования таких методов.The Universal Windows Platform (UWP) features a well-defined model for calling asynchronous methods and provides the types that you need to consume such methods. Если вы не знакомы с асинхронной моделью UWP, перед чтением этой статьи ознакомьтесь со статьей Асинхронное программирование.If you are not familiar with the UWP asynchronous model, read Asynchronous Programming before you read the rest of this article.

Хотя асинхронные API-интерфейсы среда выполнения Windows можно использовать непосредственно в C++, предпочтительным подходом является использование класса Task и связанных с ним типов и функций, которые содержатся в пространстве имен Concurrency и определены в <ppltasks.h> .Although you can consume the asynchronous Windows Runtime APIs directly in C++, the preferred approach is to use the task class and its related types and functions, which are contained in the concurrency namespace and defined in <ppltasks.h>. concurrency::task — это тип общего назначения, но если используется параметр компилятора /ZW, необходимый для компонентов и приложений универсальной платформы для Windows (UWP), то данный класс инкапсулирует асинхронные типы UWP. Благодаря этому легче:The concurrency::task is a general-purpose type, but when the /ZW compiler switch—which is required for Universal Windows Platform (UWP) apps and components—is used, the task class encapsulates the UWP asynchronous types so that it's easier to:

  • создавать цепочки из нескольких синхронных и асинхронных операций;chain multiple asynchronous and synchronous operations together

  • обрабатывать исключения в цепочках задач;handle exceptions in task chains

  • выполнять отмену в цепочках задач;perform cancellation in task chains

  • запускать отдельные задачи в нужном контексте потока или подразделении.ensure that individual tasks run in the appropriate thread context or apartment

Эта статья содержит базовое руководство по использованию класса task с асинхронными API UWP.This article provides basic guidance about how to use the task class with the UWP asynchronous APIs. Более полную документацию по задаче и ее связанным методам, включая Создание _ задачи, см. в разделе параллелизм задач (среда выполнения с параллелизмом).For more complete documentation about task and its related methods including create_task, see Task Parallelism (Concurrency Runtime).

Запуск асинхронных операций с помощью задачConsuming an async operation by using a task

В следующем примере показано, как с помощью класса task использовать метод async, который возвращает интерфейс IAsyncOperation и результатом действия которого является некоторое значение.The following example shows how to use the task class to consume an async method that returns an IAsyncOperation interface and whose operation produces a value. Ниже перечислены основные шаги.Here are the basic steps:

  1. Вызовите метод create_task и передайте ему объект IAsyncOperation^.Call the create_task method and pass it the IAsyncOperation^ object.

  2. Вызовите функцию-член task::then класса task и задайте лямбда-функцию, которая будет вызвана, когда завершится асинхронная операция.Call the member function task::then on the task and supply a lambda that will be invoked when the asynchronous operation completes.

#include <ppltasks.h>
using namespace concurrency;
using namespace Windows::Devices::Enumeration;
...
void App::TestAsync()
{    
    //Call the *Async method that starts the operation.
    IAsyncOperation<DeviceInformationCollection^>^ deviceOp =
        DeviceInformation::FindAllAsync();

    // Explicit construction. (Not recommended)
    // Pass the IAsyncOperation to a task constructor.
    // task<DeviceInformationCollection^> 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, называется задачей-продолжением.The task that's created and returned by the task::then function is known as a continuation. Входным аргументом для пользовательской лямбда-функции в данном случае является результат, который возвращает операция задачи после завершения.The input argument (in this case) to the user-provided lambda is the result that the task operation produces when it completes. Этот же результат был бы получен при вызове IAsyncOperation::GetResults, если бы вы непосредственно использовали интерфейс IAsyncOperation.It's the same value that would be retrieved by calling IAsyncOperation::GetResults if you were using the IAsyncOperation interface directly.

Возврат из метода task::then выполняется немедленно, а его делегат не выполняется до тех пор, пока асинхронная операция не завершится успешно.The task::then method returns immediately, and its delegate doesn't run until the asynchronous work completes successfully. Если в этом примере во время выполнения асинхронной операции возникнет исключение или она завершится в отмененном состоянии (в результате запроса на отмену), задача-продолжение не будет выполняться.In this example, if the asynchronous operation causes an exception to be thrown, or ends in the canceled state as a result of a cancellation request, the continuation will never execute. Позже мы рассмотрим, как писать задачи-продолжения, которые выполняются, даже если предыдущая задача завершилась неудачно или была отменена.Later, we’ll describe how to write continuations that execute even if the previous task was cancelled or failed.

Хотя вы объявляете переменную task в локальном стеке, она самостоятельно управляет своим временем существования и не удаляется до тех пор, пока все операции не будут завершены и все ссылки на нее не исчезнут из области видимости, даже если возврат из метода происходит до завершения операции.Although you declare the task variable on the local stack, it manages its lifetime so that it is not deleted until all of its operations complete and all references to it go out of scope, even if the method returns before the operations complete.

Создание цепочки задачCreating a chain of tasks

Одной из распространенных практик асинхронного программирования является задание последовательности операций, называемой цепочкой задач, в которой каждая задача-продолжение выполняется только после завершения предыдущей.In asynchronous programming, it's common to define a sequence of operations, also known as task chains, in which each continuation executes only when the previous one completes. В некоторых случаях входным параметром для дополнительной задачи является результат выполнения предыдущей задачи (называемой также задачей-предшественником).In some cases, the previous (or antecedent) task produces a value that the continuation accepts as input. Используя метод task::then, вы можете создавать цепочки задач простым и наглядным способом. Этот метод возвращает task, где T — тип значения, возвращаемого лямбда-функцией.By using the task::then method, you can create task chains in an intuitive and straightforward manner; the method returns a task where T is the return type of the lambda function. В цепочку задач можно составить несколько продолжений: myTask.then(…).then(…).then(…);You can compose multiple continuations into a task chain: myTask.then(…).then(…).then(…);

Цепочки задач удобно использовать, когда задача-продолжение создает новую асинхронную операцию. Такая задача называется асинхронной.Task chains are especially useful when a continuation creates a new asynchronous operation; such a task is known as an asynchronous task. В следующем примере показана цепочка, состоящая из двух задач-продолжений.The following example illustrates a task chain that has two continuations. Начальная задача получает дескриптор существующего файла. Когда эта операция завершается, первая задача-продолжение запускает новую асинхронную операцию, удаляющую файл.The initial task acquires the handle to an existing file, and when that operation completes, the first continuation starts up a new asynchronous operation to delete the file. Когда эта операция завершается, запускается вторая задача-продолжение, отображающая подтверждение.When that operation completes, the second continuation runs, and outputs a confirmation message.

#include <ppltasks.h>
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.");
    });
}

Приведенный пример иллюстрирует четыре важных момента.The previous example illustrates four important points:

  • Первая задача-продолжение преобразует объект IAsyncAction^ в task и возвращает task.The first continuation converts the IAsyncAction^ object to a task and returns the task.

  • Вторая задача-продолжение не производит обработку ошибок, а потому в качестве параметра принимает void, а не task.The second continuation performs no error handling, and therefore takes void and not task as input. Эта задача-продолжение основывается на значении предыдущей.It is a value-based continuation.

  • Вторая задача-продолжение не выполняется до тех пор, пока не закончится операция DeleteAsync.The second continuation doesn't execute until the DeleteAsync operation completes.

  • Поскольку вторая задача-продолжение основывается на значении предыдущей, возникновение исключения в операции, запускаемой вызовом функции DeleteAsync, приведет к тому, что вторая задача-продолжение не запустится.Because the second continuation is value-based, if the operation that was started by the call to DeleteAsync throws an exception, the second continuation doesn't execute at all.

Примечание    . Создание цепочки задач — лишь один из способов использования класса Task для составления асинхронных операций.Note  Creating a task chain is just one of the ways to use the task class to compose asynchronous operations. Операции можно также создавать с помощью операторов JOIN и Choice && || .You can also compose operations by using join and choice operators && and ||. Дополнительные сведения см. в разделе Параллелизм задач (среда выполнения с параллелизмом).For more information, see Task Parallelism (Concurrency Runtime).

Типы значений, которые возвращают лямбда-функция и задачаLambda function return types and task return types

В задаче-продолжении тип значения, которое возвращает лямбда-функция, упаковывается в объект task.In a task continuation, the return type of the lambda function is wrapped in a task object. Если лямбда-функция возвращает значение типа double, то задача-продолжение возвращает значение типа task.If the lambda returns a double, then the type of the continuation task is task. Но объект task разработан таким образом, что не возвращает без необходимости значения вложенных типов.However, the task object is designed so that it doesn't produce needlessly nested return types. Если лямбда-функция возвращает значение IAsyncOperation<SyndicationFeed^>^, задача-продолжение возвращает значение task<SyndicationFeed^>, а не task<task<SyndicationFeed^>> или task<IAsyncOperation<SyndicationFeed^>^>^.If a lambda returns an IAsyncOperation<SyndicationFeed^>^, the continuation returns a task<SyndicationFeed^>, not a task<task<SyndicationFeed^>> or task<IAsyncOperation<SyndicationFeed^>^>^. Этот процесс называется асинхронной распаковкой. Он также обеспечивает завершение асинхронной операции в задаче-продолжении до того, как будет вызвана следующая задача-продолжение.This process is known as asynchronous unwrapping and it also ensures that the asynchronous operation inside the continuation completes before the next continuation is invoked.

Обратите внимание, что в приведенном выше примере задача возвращает task, хотя ее лямбда-функция вернула объект IAsyncInfo.In the previous example, notice that the task returns a task even though its lambda returned an IAsyncInfo object. В следующей таблице приводятся типы значений, возвращаемых лямбда-функцией, и соответствующие им типы значений, возвращаемых задачей, в которую вложена лямбда-функция.The following table summarizes the type conversions that occur between a lambda function and the enclosing task:

Тип возвращаемого значения лямбда-функцииlambda return type .then Тип возвращаемого значения.then return type
TResultTResult задачиtask
IAsyncOperation^IAsyncOperation^ задачиtask
IAsyncOperationWithProgress<TResult, TProgress>^IAsyncOperationWithProgress<TResult, TProgress>^ задачиtask
IAsyncAction^IAsyncAction^ задачиtask
IAsyncActionWithProgress^IAsyncActionWithProgress^ задачиtask
задачиtask задачиtask

Отмена задачCanceling tasks

Часто бывает нужно дать пользователю возможность отменить асинхронную операцию.It is often a good idea to give the user the option to cancel an asynchronous operation. А иногда возникает необходимость отменить асинхронную операцию программным путем извне цепочки задач.And in some cases you might have to cancel an operation programmatically from outside the task chain. Хотя каждый * асинхронный тип возвращаемого значения имеет метод Cancel , который он наследует от иасинЦинфо, неудобно предоставлять его внешним методам.Although each *Async return type has a Cancel method that it inherits from IAsyncInfo, it's awkward to expose it to outside methods. Предпочтительным способом поддержки отмены в цепочке задач является использование ** _ _ источника токена отмены** для создания ** _ токена отмены**, а затем передача маркера конструктору начальной задачи.The preferred way to support cancellation in a task chain is to use a cancellation_token_source to create a cancellation_token, and then pass the token to the constructor of the initial task. Если асинхронная задача создается с токеном отмены и вызывается ** _ токен отмены _ Source:: Cancel** , задача автоматически вызывает Cancel для операции **IAsync * ** и передает запрос об отмене в цепочку продолжения.If an asynchronous task is created with a cancellation token, and cancellation_token_source::cancel is called, the task automatically calls Cancel on the IAsync* operation and passes the cancellation request down its continuation chain. Следующий псевдокод иллюстрирует этот подход.The following pseudocode demonstrates the basic approach.

//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 ...

При отмене задачи исключение ** _ отмененной задачи** распространяется вниз по цепочке задач.When a task is canceled, a task_canceled exception is propagated down the task chain. Задачи-продолжения, основанные на предыдущих значениях, просто не запустятся, а в безусловных задачах-продолжениях возникнет исключение при вызове task::get.Value-based continuations will simply not execute, but task-based continuations will cause the exception to be thrown when task::get is called. При возникновении продолжения обработки ошибок убедитесь, что он явно перехватывает исключение ** _ Отмена задачи** .If you have an error-handling continuation, make sure that it catches the task_canceled exception explicitly. (Это исключение не является производным от Platform::Exception.)(This exception is not derived from Platform::Exception.)

Отмена выполняется для всех задач.Cancellation is cooperative. Если ваша задача-продолжение не просто вызывает метод UWP, а выполняет какие-то длительные операции, необходимо периодически проверять состояние маркера отмены и останавливать выполнение при отмене.If your continuation does some long-running work beyond just invoking a UWP method, then it is your responsibility to check the state of the cancellation token periodically and stop execution if it is canceled. После очистки всех ресурсов, которые были выделены в продолжении, вызовите Отмена _ текущей _ задачи , чтобы отменить эту задачу и распространить отмену до любых продолжений на основе значений.After you clean up all resources that were allocated in the continuation, call cancel_current_task to cancel that task and propagate the cancellation down to any value-based continuations that follow it. Другой пример: вы можете создать цепочку задач, представляющую результат операции FileSavePicker.Here's another example: you can create a task chain that represents the result of a FileSavePicker operation. Если пользователь нажимает кнопку Отмена, метод IAsyncInfo::Cancel не вызывается.If the user chooses the Cancel button, the IAsyncInfo::Cancel method is not called. Вместо этого операция успешно завершается, но возвращает nullptr.Instead, the operation succeeds but returns nullptr. Продолжение может проверить входной параметр и вызвать отмену _ текущей _ задачи , если входные данные имеют значение nullptr.The continuation can test the input parameter and call cancel_current_task if the input is nullptr.

Подробнее см. в разделе Отмена в библиотеке параллельных шаблонов.For more information, see Cancellation in the PPL

Обработка ошибок в цепочках задачHandling errors in a task chain

Если вы хотите, чтобы задача-продолжение выполнялась, даже если задача-предшественник отменена или в ней возникло исключение, сделайте задачу-продолжение безусловной, указав в качестве входного параметра для ее лямбда-функции task или task (если лямбда-функция задачи-предшественника возвращает IAsyncAction^).If you want a continuation to execute even if the antecedent was canceled or threw an exception, then make the continuation a task-based continuation by specifying the input to its lambda function as a task or task if the lambda of the antecedent task returns an IAsyncAction^.

Для обработки ошибок и отмены в цепочке задач нет необходимости делать каждую задачу-продолжение безусловной или помещать каждую операцию, которая может вызвать исключение, в блок try…catch.To handle errors and cancellation in a task chain, you don't have to make every continuation task-based or enclose every operation that might throw within a try…catch block. Вместо этого вы можете добавить одну безусловную задачу-продолжение в конец цепочки и обрабатывать все ошибки в ней.Instead, you can add a task-based continuation at the end of the chain and handle all errors there. Любое исключение — это включает в себя исключение " задача _ отменена " — распространяет цепочку задач и обходит все продолжения на основе значений, чтобы вы могли обработать ее в продолжении по задаче обработки ошибок.Any exception—this includes a task_canceled exception—will propagate down the task chain and bypass any value-based continuations, so that you can handle it in the error-handling task-based continuation. Перепишем предыдущий пример с использованием безусловной задачи-продолжения, обрабатывающей ошибки:We can rewrite the previous example to use an error-handling task-based continuation:

#include <ppltasks.h>
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<void> 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());
        }

    });
}

В безусловной задаче-продолжении для получения результатов задачи мы вызываем функцию-член task::get.In a task-based continuation, we call the member function task::get to get the results of the task. Нам все еще нужно вызывать task::get, даже если операция имела тип IAsyncAction и не возвращала результат, так как task::get также перехватывает все исключения, проходящие по цепочке задач.We still have to call task::get even if the operation was an IAsyncAction that produces no result because task::get also gets any exceptions that have been transported down to the task. Если входная задача сохраняет исключение, то оно возникает при вызове task::get.If the input task is storing an exception, it is thrown at the call to task::get. Если вы не вызываете Task:: Getили не используете продолжение на основе задачи в конце цепочки или не перехватите созданного типа исключения, то при удалении всех ссылок на задачу возникает ** _ _ исключение незамеченной задачи** .If you don't call task::get, or don't use a task-based continuation at the end of the chain, or don't catch the exception type that was thrown, then an unobserved_task_exception is thrown when all references to the task have been deleted.

Перехватывайте только те исключения, которые можете обработать.Only catch the exceptions that you can handle. Если в приложении возникает ошибка, которую не удается устранить, лучше дать программе аварийно завершиться, чем оставить ее работать в неизвестном состоянии.If your app encounters an error that you can't recover from, it's better to let the app crash than to let it continue to run in an unknown state. Кроме того, в общем случае не пытайтесь перехватить ** _ _ исключение незамеченной задачи** .Also, in general, don't attempt to catch the unobserved_task_exception itself. Оно предназначено главным образом для диагностики.This exception is mainly intended for diagnostic purposes. При возникновении ** _ _ исключения незамеченной задачи** оно обычно указывает на ошибку в коде.When unobserved_task_exception is thrown, it usually indicates a bug in the code. Причиной этого часто является либо исключение, которое должно быть обработано, либо неустранимое исключение, вызванное какой-то другой ошибкой в коде.Often the cause is either an exception that should be handled, or an unrecoverable exception that's caused by some other error in the code.

Управление контекстом потокаManaging the thread context

Пользовательский интерфейс приложений UWP работает в однопотоковом подразделении (STA).The UI of a UWP app runs in a single-threaded apartment (STA). Задача, лямбда-функция которой возвращает IAsyncAction или IAsyncOperation, поддерживает подразделения.A task whose lambda returns either an IAsyncAction or IAsyncOperation is apartment-aware. Если задача создается в однопотоковом подразделении, то если вы не укажете иное, в нем же по умолчанию работают и все ее продолжения.If the task is created in the STA, then all of its continuations will run also run in it by default, unless you specify otherwise. Другими словами, вся цепочка задач наследует модель работы в подразделении от родительской задачи.In other words, the entire task chain inherits apartment-awareness from the parent task. Это упрощает взаимодействие с пользовательским интерфейсом, доступ к которому возможен только из STA.This behavior helps simplify interactions with UI controls, which can only be accessed from the STA.

Например, в функции-члене любого класса, представляющего страницу XAML, в приложении UWP вы можете заполнить элемент управления ListBox в методе task::then, не используя объект Dispatcher.For example, in a UWP app, in the member function of any class that represents a XAML page, you can populate a ListBox control from within a task::then method without having to use the Dispatcher object.

#include <ppltasks.h>
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, то она не поддерживает подразделения и по умолчанию ее задачи-продолжения работают в первом доступном фоновом потоке.If a task doesn't return an IAsyncAction or IAsyncOperation, then it's not apartment-aware and, by default, its continuations are run on the first available background thread.

Можно переопределить контекст потока по умолчанию для любой из видов задач, используя перегрузку Task:: then , которая принимает ** _ _ контекст продолжения задачи**.You can override the default thread context for either kind of task by using the overload of task::then that takes a task_continuation_context. Например, в некоторых случаях для задачи, поддерживающей подразделения, желательно запланировать работу задачи-продолжения в фоновом потоке.For example, in some cases, it might be desirable to schedule the continuation of an apartment-aware task on a background thread. В этом случае можно передать ** _ контекст продолжения задачи _ :: использовать _ произвольный** параметр, чтобы запланировать работу задачи в следующем доступном потоке в многопоточном апартаменте.In such a case, you can pass task_continuation_context::use_arbitrary to schedule the task’s work on the next available thread in a multi-threaded apartment. Это может повысить производительность задачи-продолжения, поскольку ее работу не нужно синхронизировать с другими действиями в потоке пользовательского интерфейса.This can improve the performance of the continuation because its work doesn't have to be synchronized with other work that's happening on the UI thread.

В следующем примере показано, когда можно указать ** _ контекст продолжения задачи _ : использовать _ произвольный** параметр, а также показывает, как контекст продолжения по умолчанию полезен для синхронизации параллельных операций с коллекциями, не являющимися потокобезопасными.The following example demonstrates when it's useful to specify the task_continuation_context::use_arbitrary option, and it also shows how the default continuation context is useful for synchronizing concurrent operations on non-thread-safe collections. В приведенном фрагменте кода мы циклически обрабатываем список URL-адресов для RSS-каналов и для каждого адреса запускаем асинхронную операцию, чтобы получить данные этого веб-канала.In this code, we loop through a list of URLs for RSS feeds, and for each URL, we start up an async operation to retrieve the feed data. Мы не можем контролировать порядок, в котором обрабатываются веб-каналы, и это нас не интересует.We can’t control the order in which the feeds are retrieved, and we don't really care. После завершения каждой операции RetrieveFeedAsync первая задача-продолжение принимает объект SyndicationFeed^ и использует его для инициализации установленного объекта FeedData^ приложения.When each RetrieveFeedAsync operation completes, the first continuation accepts the SyndicationFeed^ object and uses it to initialize an app-defined FeedData^ object. Так как каждая из этих операций независима от других, мы можем ускорить процесс, указав ** _ контекст продолжения задачи _ :: использовать _ произвольный** контекст продолжения.Because each of these operations is independent from the others, we can potentially speed things up by specifying the task_continuation_context::use_arbitrary continuation context. Но после инициализации каждого объекта FeedData нам нужно добавить его в Vector, который не является потокобезопасной коллекцией.However, after each FeedData object is initialized, we have to add it to a Vector, which is not a thread-safe collection. Поэтому мы создаем продолжение и указываем ** _ контекст продолжения задачи _ :: использовать _ Current** , чтобы гарантировать, что все вызовы, добавляемые в один и тот же контекст однопотокового подразделения (ASTA), будут выполняться в одном приложении.Therefore, we create a continuation and specify task_continuation_context::use_current to ensure that all the calls to Append occur in the same Application Single-Threaded Apartment (ASTA) context. Так как _ контекст продолжения задачи _ : использовать _ значение по умолчанию является контекстом по умолчанию, нам не нужно явно указывать его, но мы делаем это для ясности.Because task_continuation_context::use_default is the default context, we don’t have to specify it explicitly, but we do so here for the sake of clarity.

#include <ppltasks.h>
void App::InitDataSource(Vector<Object^>^ feedList, vector<wstring> urls)
{
                using namespace concurrency;
    SyndicationClient^ client = ref new SyndicationClient();

    std::for_each(std::begin(urls), std::end(urls), [=,this] (std::wstring url)
    {
        // Create the async operation. feedOp is an
        // IAsyncOperationWithProgress<SyndicationFeed^, RetrievalProgress>^
        // but we don't handle progress in this example.

        auto feedUri = ref new Uri(ref new String(url.c_str()));
        auto feedOp = client->RetrieveFeedAsync(feedUri);

        // Create the task object and pass it the async operation.
        // SyndicationFeed^ is the type of the return value
        // that the feedOp operation will eventually produce.

        // Then, initialize a FeedData object by using the feed info. Each
        // operation is independent and does not have to happen on the
        // UI thread. Therefore, we specify use_arbitrary.
        create_task(feedOp).then([this]  (SyndicationFeed^ feed) -> FeedData^
        {
            return GetFeedData(feed);
        }, task_continuation_context::use_arbitrary())

        // Append the initialized FeedData object to the list
        // that is the data source for the items collection.
        // This all has to happen on the same thread.
        // By using the use_default context, we can append
        // safely to the Vector without taking an explicit lock.
        .then([feedList] (FeedData^ fd)
        {
            feedList->Append(fd);
            OutputDebugString(fd->Title->Data());
        }, task_continuation_context::use_default())

        // The last continuation serves as an error handler. The
        // call to get() will surface any exceptions that were raised
        // at any point in the task chain.
        .then( [this] (task<void> t)
        {
            try
            {
                t.get();
            }
            catch(Platform::InvalidArgumentException^ e)
            {
                //TODO handle error.
                OutputDebugString(e->Message->Data());
            }
        }); //end task chain

    }); //end std::for_each
}

Вложенные задачи (то есть задачи, которые создаются внутри задачи-продолжения) не наследуют у исходной задачи модель работы в подразделении.Nested tasks, which are new tasks that are created inside a continuation, don't inherit apartment-awareness of the initial task.

Обработка информации о ходе выполнения операцииHanding progress updates

Методы, поддерживающие IAsyncOperationWithProgress или IAsyncActionWithProgress, могут периодически передавать информацию о ходе выполнения операции.Methods that support IAsyncOperationWithProgress or IAsyncActionWithProgress provide progress updates periodically while the operation is in progress, before it completes. Эта информация передается независимо от состояния задач и задач-продолжений.Progress reporting is independent from the notion of tasks and continuations. Нужно просто указать делегат для свойства Progress объекта.You just supply the delegate for the object’s Progress property. Этот делегат обычно используется для обновления индикатора выполнения в пользовательском интерфейсе.A typical use of the delegate is to update a progress bar in the UI.