Асинхронное программирование на языке 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.

В этой статье описывается рекомендуемый способ использовать асинхронные методы в визуальном элементе 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. Если вы не знакомы с асинхронной моделью универсальной платформы Windows, прочитайте асинхронное программирование перед чтением оставшейся части этой статьи.If you are not familiar with the UWP asynchronous model, read Asynchronous Programming before you read the rest of this article.

Несмотря на то, что вы можете использовать асинхронные API UWP непосредственно в C++, предпочтительным подходом является использование класс task and its related types and functions, which are contained in the concurrency пространства имен, определенных в <ppltasks.h>.Although you can consume the asynchronous UWP 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. Для получения дополнительных сведений выполните документации о задачи и его связанные методы, в том числе создать_задачи, see Task Parallelism (Concurrency Runtime).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

В следующем примере показано, как использование класса задачи для использования 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 щелкните задачу и поставок лямбда-выражение, которое будет вызываться при завершении асинхронной операции.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 метод, можно создавать цепочки задач интуитивно понятным и простым образом; метод возвращает задачи где 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 ^ объект задачи и возвращает задачи.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.

Примечание  создания цепочки задач — один из способов использования задачи класс для создания асинхронных операций.Note  Creating a task chain is just one of the ways to use the task class to compose asynchronous operations. Кроме того, можно создавать операции с помощью операторов подключения и выбора && и || .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.

В предыдущем примере обратите внимание на то, что задача возвращает задачи несмотря на то, что его лямбда-выражение возвращается 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. Несмотря на то что каждый * Async возвращаемого типа отменить method that it inherits from IAsyncInfo, она становится менее приемлемой для предоставлять его внешним методам.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 вызове задачи автоматически вызывает Отмена на Task 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 ...

Если задача отменяется, задачи_отменено exception is propagated down the task chain. Value-based continuations will simply not execute, but task-based continuations will cause the exception to be thrown when task::get вызывается.When a task is canceled, a task_canceled exception is propagated down the task chain. 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

Если требуется продолжение для выполнения, даже в том случае, если предшествующая задача была отменена или создала исключение, внесите продолжение продолжение на основе задач путем указания входных данных для его лямбда-функции, как задачи или задачи возвращает лямбда-выражения предшествующей задачи 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. Кроме того, как правило, не пытайтесь catch непредвиденное_задачи_исключение сам.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 or 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.

Например, в приложении универсальной платформы Windows, в функцию-член класса, который представляет страницу XAML, можно заполнить 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 or 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. В этом случае можно передать задачи_продолжения_context::use_произвольные для планирования работы задачи на следующем доступном потоке в многопотокового подразделения.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.

В следующем примере показано, когда это полезно для указания задачи_продолжения_context::use_произвольные и она также показано, как контекст продолжения по умолчанию можно использовать для синхронизации параллельных операций с коллекциями, не являющихся потокобезопасными.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. Поскольку каждой из этих операций не зависит от других, мы можем потенциально ускорения процесса копирования, указав задачи_продолжения_context::use_произвольные контекст продолжения .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. Таким образом, создать продолжение и укажите задачи_продолжения_context::use_текущей чтобы убедиться, что все вызовы Append происходят в том же контексте подразделения Application Single-Threaded (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. Так как задачи_продолжения_context::use_по умолчанию является контекст по умолчанию, нам не нужно указывать явно, но мы здесь сделать это для sake из для ясности.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.