Использование C++ в примере Bing Maps Trip Optimizer
В этом документе описывается компонент C++ приложения Bing Maps Trip Optimizer. Компонент C++ использует чисто машинный код для реализации логики оптимизации маршрута и код Расширения компонентов Visual C++ (C++/CX) для взаимодействия с другими компонентами Среда выполнения Windows.
Примечание
Дополнительные сведения о C++/CX, расширениях языка, которые доступны приложениям Магазин Windows, написанным на языке C++, см. в разделе Справочник по языку C++ (C++/CX).
Дополнительные сведения о создании базового компонента Среда выполнения Windows C++ см. в разделе Пошаговое руководство. Создание основного компонента среды выполнения Windows в C++ и его вызов из кода JavaScript или C#.
C++-часть приложения написана в виде библиотеки динамической компоновки (DLL) Среда выполнения Windows. Эта библиотека предоставляет следующие функциональные возможности.
Получение широты и долготы каждого местоположения от веб-службы Bing Maps. Эта операция использует интерфейс Bing Maps Representational State Transfer (REST).
Получение расстояния движения между каждой возможной парой точек маршрута. Эта операция также использует интерфейс Bing Maps REST.
Выполнение оптимизации маршрута. Эта операция использует алгоритм оптимизации "колония муравьев" и параллельную обработку для эффективного вычисления оптимизированного маршрута.
Эти операции выполняются асинхронно, то есть в фоновом режиме. Благодаря этому пользовательский интерфейс продолжает отвечать на запросы в процессе оптимизации.
Ниже приведены некоторые основные функции и технологии, используемые компонентом C++.
concurrency::create_async для создания объекта Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>. Приложение JavaScript использует этот объект для отслеживания хода выполнения задачи оптимизации, а также ее завершения и отмены. concurrency::task для взаимодействия с Bing Maps и выполнения задачи оптимизации в фоновом режиме.
Службы Bing Maps REST для извлечения сведений о расположении и маршруте.
IXMLHTTPRequest2 для асинхронного запроса и считывания HTTP-потоков из службы Bing Maps REST.
Windows::Data::Xml::Dom::XmlDocument для считывания откликов веб-службы Bing Maps.
Алгоритм concurrency::parallel_for_each для параллельного выполнения алгоритма оптимизации маршрута.
Примечание
Пример кода, соответствующий этому документу, входит в состав примера приложения Bing Maps Trip Optimizer.
Содержание этого документа
Соглашения о коде
Организация файлов
Создание классов TripOptimizer и TripOptimizerImpl
Рабочий процесс компонента
Этап 1. Получение данных о местоположениях
Этап 2. Получение данных о маршруте
Этап 3. Вычисление оптимизированного маршрута
Определение функциональности HTTP
Вычисление оптимального маршрута
Обработка отмены
Переход с версии ActiveX
Следующие шаги
Соглашения о коде
В дополнение к использованию Среда выполнения Windows для создания компонента Магазин Windows, компонент C++ использует современные соглашения о написании кода, такие как интеллектуальные указатели и обработка исключений.
Среда выполнения Windows — это интерфейс программирования, который можно использовать для создания приложений Магазин Windows, выполняющихся только в доверенной среде операционной системы. Такие приложения используют авторизованные функции, типы данных и устройства и распространяются через Магазин Windows. Среда выполнения Windows представлена двоичным интерфейсом приложений (ABI). Интерфейс ABI является базовым двоичным контрактом, который делает интерфейсы API Среда выполнения Windows доступными для языков программирования, таких как Visual C++.
Чтобы упростить написание приложений, использующих Среда выполнения Windows, корпорация Майкрософт предоставляет расширения языка Visual C++, предназначенных для поддержки Среда выполнения Windows. Многие из этих расширений языка напоминают синтаксис языка C++/CLI. Однако в данном случае этот синтаксис служит для создания собственных приложений для платформы Среда выполнения Windows, а для среды CLR. Модификатор в виде крышки (^) — важная часть этого нового синтаксиса, так как он позволяет автоматически удалять объекты среды выполнения путем подсчета ссылок. Вместо того чтобы вызывать для управления временем жизни объекта Среда выполнения Windows такие методы, как AddRef и Release, среда выполнения удаляет объект, если на него больше не ссылается ни один другой компонент, например когда он покидает область или всем ссылкам присвоено значение nullptr. Еще один важный аспект использования Visual C++ для создания приложений Магазин Windows — ключевое слово ref new. Ключевое слово ref new используется вместо ключевого слова new для создания объектов Среда выполнения Windows со счетчиком ссылок. Дополнительные сведения см. в разделе Объекты среды выполнения Windows и ref new.
Важно!
Модификатор ^ и ключевое слово ref new обязательно использовать только при создании объектов Среда выполнения Windows или компонентов Среда выполнения Windows.При написании основного кода приложения, не использующего Среда выполнения Windows, можно использовать стандартный синтаксис C++.
Примечание
Приложение Bing Maps Trip Optimizer использует модификатор ^ вместе с указателями Microsoft::WRL::ComPtr, std::shared_ptr и std::unique_ptr для управления выделенными в куче объектами и уменьшения утечек памяти.Рекомендуется использовать ^ для управления временем жизни переменных Среда выполнения Windows, указатель Microsoft::WRL::ComPtr для управления временем жизни переменных COM, а указатель shared_ptr или unique_ptr для управления временем жизни всех остальных объектов C++, размещенных в куче.
Дополнительные сведения о современных особенностях программирования на языке C++ см. в разделе Возвращение к C++ (современный C++). Дополнительные сведения о расширениях языка, доступных для приложения C++ Магазин Windows, см. в разделе Справочник по языку C++ (C++/CX).
[Наверх]
Организация файлов
В следующем списке кратко описывается роль каждого файла исходного кода, являющегося частью компонента C++.
AntSystem.h, AntSystem.cpp
Определяет алгоритм оптимизации "колония муравьев" и поддерживающие его структуры данных.HttpRequest.h, HttpRequest.cpp
Определяет класс HttpRequest, являющийся вспомогательным классом для выполнения асинхронных HTTP-запросов.pch.h, pch.cpp
Предкомпилированный заголовок для проекта.TripOptimizer.h, TripOptimizer.cpp
Определяет класс TripOptimizer, который служит интерфейсом между приложением и основной логикой компонента.TripOptimizerImpl.h, TripOptimizerImpl.cpp
Определяет класс TripOptimizerImpl, определяющий основную логику компонента.
[Наверх]
Создание классов TripOptimizer и TripOptimizerImpl
Компонент C++ содержит класс Среда выполнения Windows — TripOptimizerComponent::TripOptimizer — который может взаимодействовать с другими компонентами Среда выполнения Windows. В приложении Bing Maps Trip Optimizer этот класс взаимодействует с JavaScript-частью приложения. (Поскольку компонент C++ написан как библиотека DLL, ее можно также использовать с другими приложениями). Класс TripOptimizer определяет только методы, взаимодействующие с другими компонентами Среда выполнения Windows. Детальная реализация выполняется классом TripOptimizerImpl. Мы выбрали эту модель для лучшей инкапсуляции открытого интерфейса и его отделения от детальной реализации. Дополнительные сведения об этой модели см. в разделе Pimpl для инкапсуляции времени компиляции (современный C++).
// Defines the TripOptimizer class. This class interfaces between the app
// and the implementation details.
public ref class TripOptimizer sealed
{
public:
TripOptimizer();
// Optimizes a trip as an asynchronous process.
Windows::Foundation::IAsyncOperationWithProgress<
Windows::Foundation::Collections::IMap<
Platform::String^,
Windows::Foundation::Collections::IVector<Platform::String^>^>^,
Platform::String^>^ OptimizeTripAsync(
Windows::Foundation::Collections::IVector<Platform::String^>^ waypoints,
Platform::String^ travelMode,
Platform::String^ optimize,
Platform::String^ bingMapsKey,
double alpha, double beta, double rho,
unsigned int iterations, bool parallel);
private:
~TripOptimizer();
// Defines implementation details of the optimization routine.
std::unique_ptr<Details::TripOptimizerImpl> m_impl;
};
Важно!
При создании класса компонента Среда выполнения Windows, который можно использовать совместно с другими внешними компонентами, убедитесь, что используются ключевые слова public и sealed.Дополнительные сведения о создании компонента Среда выполнения Windows в C++, который может быть вызван из приложения Магазин Windows, см. в разделе Создание компонентов среды выполнения Windows в C++.
Метод TripOptimizer::OptimizeTripAsync определяет взаимодействие приложения с компонентом C++. Этот метод запускает последовательность задач, которые вычисляют оптимизированный маршрут. Вызывающий компонент использует возвращаемое методом значение для отслеживания хода выполнения задачи оптимизации и ее завершения. Оно также используется для отмены операции. Операция отмены объясняется в разделе Обработка отмены далее в этом документе.
Метод TripOptimizer::OptimizeTripAsync откладывает выполнение классом TripOptimizerImpl своего действия. TripOptimizerImpl более подробно описывается далее в этом документе.
// Optimizes a trip as an asynchronous process.
IAsyncOperationWithProgress<IMap<String^, IVector<String^>^>^, String^>^
TripOptimizer::OptimizeTripAsync(
IVector<String^>^ waypoints,
String^ travelMode,
String^ optimize,
String^ bingMapsKey,
double alpha, double beta, double rho,
unsigned int iterations, bool parallel)
{
// Forward to implementation.
return m_impl->OptimizeTripAsync(waypoints,travelMode, optimize, bingMapsKey,
alpha, beta, rho, iterations, parallel);
}
[Наверх]
Рабочий процесс компонента
Метод TripOptimizer::OptimizeTripAsync запускает последовательность операций, которые вычисляют оптимизированный маршрут. Этот метод работает асинхронно, чтобы предоставить приложению возможность продолжать отвечать на запросы. Для включения асинхронного поведения этот метод возвращает значение Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>. Компонент Среда выполнения Windows, который вызывает этот метод, может использовать данный объект для получения возвращаемого значения, если оно доступно. Этот интерфейс также позволяет вызывающему объекту отслеживать ход выполнения операции и получать все возникающие ошибки.
Совет
Рекомендуется завершать имя асинхронного метода Async, а также чтобы метод возвращал значение Windows::Foundation::IAsyncAction, Windows::Foundation::IAsyncActionWithProgress<TProgress>, Windows::Foundation::IAsyncOperation<TResult> или Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>.
Каждый язык, поддерживающий Среда выполнения Windows (C++, JavaScript и т. д.), предписывает собственный способ создания асинхронной операции. В C++ можно использовать функцию concurrency::create_async. Эта функция возвращает объект IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> или IAsyncOperationWithProgress<TResult, TProgress>. Тип возвращаемого значения зависит от сигнатуры объекта, передаваемого функции. Например, поскольку метод TripOptimizerImpl::InternalOptimizeTripAsync принимает объект concurrency::progress_reporter в качестве параметра и возвращает значение, отличное от void, функция create_async возвращает объект IAsyncOperationWithProgress<TResult, TProgress>. Если бы этот метод возвращал значение void, функция create_async возвращала бы объект IAsyncActionWithProgress<TProgress>. Дополнительные сведения о функции create_async с ее работе в Windows 8 см. в разделе Создание асинхронных операций в C++ для приложений для Магазина Windows.
В следующем примере кода показан метод TripOptimizerImpl::OptimizeTripAsync.
// Optimizes a trip as an asynchronous process.
IAsyncOperationWithProgress<IMap<String^, IVector<String^>^>^, String^>^
TripOptimizerImpl::OptimizeTripAsync(
IVector<String^>^ waypoints,
String^ travelMode,
String^ optimize,
String^ bingMapsKey,
double alpha, double beta, double rho,
unsigned int iterations, bool parallel)
{
// Copy inputs to a OptimizeTripParams structure.
auto params = make_shared<OptimizeTripParams>();
for (auto waypoint : waypoints)
{
params->Waypoints.push_back(waypoint->Data());
}
params->TravelMode = wstring(travelMode->Data());
params->Optimize = wstring(optimize->Data());
params->BingMapsKey = UriEncode(bingMapsKey->Data());
params->Alpha = alpha;
params->Beta = beta;
params->Rho = rho;
params->Iterations = iterations;
params->Parallel = parallel;
// Perform the operation asynchronously.
return create_async([this, params](progress_reporter<String^> reporter,
cancellation_token cancellationToken) -> task<IMap<String^, IVector<String^>^>^>
{
// Create a linked source for cancellation.
// This enables both the caller (through the returned
// IAsyncOperationWithProgress object) and this class to set
// the same cancellation token.
m_cancellationTokenSource =
cancellation_token_source::create_linked_source(cancellationToken);
// Perform the trip optimization.
return InternalOptimizeTripAsync(params, cancellationToken, reporter)
.then([this](task<IMap<String^, IVector<String^>^>^> previousTask) -> IMap<String^, IVector<String^>^>^
{
try
{
return previousTask.get();
}
catch (const task_canceled&)
{
return nullptr;
}
});
});
}
Объект progress_reporter передает сообщения о ходе выполнения вызывающему объекту. Объект cancellation_token позволяет компоненту отвечать на запросы отмены. (Операция отмены описывается в разделе Обработка отмены далее в этом документе.) Дополнительные сведения о том, как часть приложения, написанная на JavaScript, работает с асинхронной операцией, возвращаемой методом TripOptimizer::OptimizeTripAsync, см. в разделе Взаимодействие кода JavaScript и C++ в примере Bing Maps Trip Optimizer.
Рабочая функция, предоставляемая функции create_async в методе TripOptimizer::OptimizeTripAsync, возвращает объект task. Из функции create_async можно вернуть значение T или задачу task<T>. Мы возвращаем задачу task<T>, чтобы не требовалось ожидать результат выполнения фоновых задач. Вместо этого мы рекомендуем позволить среде выполнения получить результат, когда он станет доступным, и передать его вызывающему объекту. Рекомендуется по возможности использовать эту модель.
Файл TripOptimizer.cpp определяет вспомогательную функцию task_from_result, возвращающую объект task, который немедленно завершается указанным результатом. Эта функция полезна при написании функции, которая возвращает задачу task и объект, возвращаемый функцией ранее с определенным результатом.
// Creates a task that completes with the provided result.
template <typename Result>
task<Result> task_from_result(Result result)
{
return create_task([result]() -> Result { return result; });
}
На следующем рисунке показан поток операций, которые возникают, когда внешний компонент вызывает метод TripOptimizer::OptimizeTripAsync для запуска процесса оптимизации.
Метод TripOptimizerImpl::InternalOptimizeTripAsync вызывает метод TripOptimizerImpl::OptimizeTripAsync как часть асинхронной операции. Метод TripOptimizerImpl::CreateGraph вызывает метод TripOptimizerImpl::InternalOptimizeTripAsync, чтобы создать граф, который представляет маршрут. Каждое местоположение представляется узлом, и каждая пара узлов соединена границей. Узел содержит сведения о местоположении, например его имя, широта и долгота. Граница содержит расстояние движения между парой узлов.
// Creates the graph of objects that represents the trip topography.
void TripOptimizerImpl::CreateGraph(
const vector<wstring>& waypoints,
vector<shared_ptr<Node>>& nodes,
vector<shared_ptr<Edge>>& edges)
{
//
// Create a Node object for each waypoint in the array.
// Each element of the waypoints array contains a string that represents
// a location (for example, "Space Needle, WA").
//
for (const wstring& waypoint : waypoints)
{
// Add a Node object to the collection.
nodes.push_back(make_shared<Node>(waypoint));
}
//
// Create edges that form a fully-connected graph.
//
// Connect every node to every other node.
for (auto iter = begin(nodes); iter != end(nodes); ++iter)
{
auto node1 = *iter;
for_each(iter + 1, end(nodes), [this, &node1, &edges](shared_ptr<Node> node2)
{
// Create edge pair.
edges.push_back(make_shared<Edge>(node1, node2));
});
}
}
Метод TripOptimizerImpl::InternalOptimizeTripAsync выполняет оптимизацию маршрута в 3 этапа. На первом этапе этот метод получает данные о местоположении из службы Bing Maps. На втором этапе метод извлекает сведения о маршруте между каждой возможной парой точек общего маршрута. На третьем этапе метод выполняет алгоритм оптимизации маршрута. Каждый этап выполняется как продолжение задач предыдущего шага. Продолжения позволяют выполнять одну или несколько задач после завершения другой задачи. Более подробное описание продолжений приведено далее в этом документе. Но обратите внимание: поскольку продолжение выполняется в фоновом режиме, необходимо хранить переменные, которые совместно используются задачами, чтобы задачи могли позднее обращаться к ним. Класс TripOptimizerImpl определяет структуру OptimizeTripParams. Эта структура содержит входные данные для метода TripOptimizerImpl::InternalOptimizeTripAsync и переменные, совместно используемые задачами, которые составляют общую операцию.
// Holds variables that are used throughout the trip optimization process.
// We create this stucture so that common parameters can be easily passed among
// task continuations.
struct OptimizeTripParams
{
//
// The inputs to OptimizeTripAsync
std::vector<std::wstring> Waypoints;
std::wstring TravelMode;
std::wstring Optimize;
std::wstring BingMapsKey;
double Alpha;
double Beta;
double Rho;
unsigned long Iterations;
bool Parallel;
//
// Timer variables
// The following times are sent as part of a progress message.
// The overall time.
ULONGLONG TotalTime;
// The overall time and the spent performing HTTP requests.
ULONGLONG HttpTime;
// The time spent performing the optimization algorithm.
ULONGLONG SimulationTime;
//
// Location graph.
// A collection of Node objects. There is one Node object for each location.
std::vector<std::shared_ptr<AntSystem::Node>> Nodes;
// A collection of Edge objects. There are
// (n * (n - 1) / 2) edges, where n is the number of nodes.
std::vector<std::shared_ptr<AntSystem::Edge>> Edges;
// The number of pending HTTP requests for the current batch.
long RequestsPending;
// Holds the unresolved locations for the first phase of the optimization process.
concurrency::concurrent_vector<std::shared_ptr<AntSystem::Node>> UnresolvedLocations;
};
Метод TripOptimizerImpl::OptimizeTripAsync создает структуру OptimizeTripParams (с помощью объекта std::shared_ptr) и передает ее каждому продолжению в цепочке задач.
// Copy inputs to a OptimizeTripParams structure.
auto params = make_shared<OptimizeTripParams>();
for (auto waypoint : waypoints)
{
params->Waypoints.push_back(waypoint->Data());
}
params->TravelMode = wstring(travelMode->Data());
params->Optimize = wstring(optimize->Data());
params->BingMapsKey = UriEncode(bingMapsKey->Data());
params->Alpha = alpha;
params->Beta = beta;
params->Rho = rho;
params->Iterations = iterations;
params->Parallel = parallel;
Примечание
Можно было создать эти переменные как прямые члены класса TripOptimizerImpl.Однако предоставление структуры параметров позволяет более четко определить связь состояния операции оптимизации маршрута с его действием.При использовании переменных-членов было бы сложнее поддерживать такие функции, как параллельное выполнение нескольких операций оптимизации маршрута.
Этап 1. Получение данных о местоположениях
На первом этапе метод TripOptimizerImpl::InternalOptimizeTripAsync получает данные о местоположении из службы Bing Maps. Данные о местоположения извлекаются для того, чтобы пользователь мог подтвердить, что служба Bing Maps получила правильные входные данные. Например, если указать "Pittsburgh", службе Bing Maps не удастся определить, какое местоположение имеется в виду: "Pittsburgh, PA", "Pittsburgh, ON" или "Pittsburgh, GA". Позднее компонент C++ должен будет получить расстояние движения между каждой парой местоположений. Поэтому, если одно из местоположений является неоднозначным, в пользовательском интерфейсе должны отображаться все возможные местоположения, чтобы пользователь мог выбрать одно из них или ввести новое. Неразрешенные местоположения отслеживаются в переменной OptimizeTripParams::UnresolvedLocations. Если метод TripOptimizerImpl::RetrieveLocationsAsync заполняет данный вектор какими-либо значениями, метод TripOptimizerImpl::InternalOptimizeTripAsync возвращает их.
//
// Phase 1: Retrieve the location of each waypoint.
//
//
params->HttpTime = GetTickCount64();
// Report progress.
reporter.report("Retrieving locations (0% complete)...");
auto tasks = RetrieveLocationsAsync(params, cancellationToken, reporter);
// Move to the next phase after all current tasks complete.
return when_all(begin(tasks), end(tasks)).then(
[=](task<void> t) -> task<IMap<String^, IVector<String^>^>^>
{
// The PPL requires that all exceptions be caught and handled.
// If any task in the collection errors, ensure that all errors in the entire
// collection are observed at least one time.
try
{
// Calling task.get will cause any exception that occurred
// during the task to be thrown.
t.get();
}
catch (Exception^)
{
observe_all_exceptions<void>(begin(tasks), end(tasks));
// Rethrow the original exception.
throw;
}
catch (const task_canceled&)
{
observe_all_exceptions<void>(begin(tasks), end(tasks));
// Rethrow the original exception.
throw;
}
// Report progress.
reporter.report("Retrieving locations (100% complete)...");
// If there are unresolved locations, return them.
if (params->UnresolvedLocations.size() > 0)
{
// Parallel arrays of input names to their possible resolved names.
Vector<String^>^ inputNames;
Vector<String^>^ options;
auto locations = ref new Map<String^, IVector<String^>^>();
// For each unresolved location, add the user input name and
// the available options to the arrays.
for (size_t i = 0; i < params->UnresolvedLocations.size(); ++i)
{
auto node = params->UnresolvedLocations[i];
// Add options.
options = ref new Vector<String^>();
for (size_t j = 0; j < node->Names.size(); ++j)
{
options->Append(ref new String(node->Names[j].c_str()));
}
locations->Insert(ref new String(node->InputName.c_str()), options);
}
return task_from_result<IMap<String^, IVector<String^>^>^>(locations);
}
Метод TripOptimizerImpl::RetrieveLocationsAsync использует класс concurrency::task для обработки HTTP-запросов в качестве фоновых задач. Поскольку класс HttpRequest работает асинхронно, метод TripOptimizerImpl::RetrieveLocationsAsync содержит все фоновые задачи и использует функцию concurrency::when_all для определения операций, которые должны выполняться после их завершения. Поскольку метод TripOptimizerImpl::RetrieveLocationsAsync является частью общей фоновой задачи, эта цепочка задач асинхронных операций не блокирует основное приложение.
Сведения о работе класса HttpRequest приведены в разделе Определение функциональности HTTP в этом документе.
// Retrieves information about the locations from the Bing Maps location service.
vector<task<void>> TripOptimizerImpl::RetrieveLocationsAsync(
shared_ptr<OptimizeTripParams> params,
cancellation_token cancellationToken,
progress_reporter<String^> reporter)
{
// Holds the tasks that process the returned XML documents.
vector<task<void>> tasks;
// Create HTTP requests for location information.
auto nodes = params->Nodes;
params->RequestsPending = static_cast<long>(params->Nodes.size()); // Used to report progress.
for (auto node : nodes)
{
wstringstream uri;
uri << L"http://dev.virtualearth.net/REST/v1/Locations?q="
<< UriEncode(node->InputName)
<< L"&o=xml&key=" << params->BingMapsKey;
// Create a parent task that downloads the XML document.
auto httpRequest = make_shared<HttpRequest>();
auto downloadTask = httpRequest->GetAsync(
ref new Uri(ref new String(uri.str().c_str())),
cancellationToken);
// Create a continuation task that fills location information after
// the download finishes.
tasks.push_back(downloadTask.then([=](task<wstring> previousTask)
{
(void)httpRequest;
// Get the result to force exceptions to be thrown.
wstring response = previousTask.get();
try
{
// Create and load the XML document from the response.
XmlDocument^ xmlDocument = ref new XmlDocument();
auto xml = ref new String(response.c_str() + 1); // Bypass BOM.
xmlDocument->LoadXml(xml);
// Fill in location information.
ProcessLocation(node, xmlDocument, params->UnresolvedLocations);
}
catch (Exception^)
{
// An error occurred. Cancel any active operations.
m_cancellationTokenSource.cancel();
// Rethrow the exception.
throw;
}
// Report progress.
wstringstream progress;
progress << L"Retrieving locations ("
<< static_cast<long>(100.0 * (params->Nodes.size() - params->RequestsPending) / params->Nodes.size())
<< L"% complete)...";
reporter.report(ref new String(progress.str().c_str()));
InterlockedDecrement(¶ms->RequestsPending);
}));
}
return tasks;
}
Метод TripOptimizerImpl::RetrieveLocationsAsync также использует метод concurrency::task::then для немедленной обработки каждого ответа, полученного от Bing Maps. Метод task::then создает задачу продолжения, которая выполняется после завершения предыдущей, или предшествующей, задачи. Вызов функции when_all в конце метода TripOptimizerImpl::RetrieveLocationsAsync ожидает завершения всех задач и их задач продолжения. Дополнительные сведения о задачах и продолжениях в C++ см. в разделе Параллелизм задач.
API службы Bing Maps REST возвращает XML-данные. Метод TripOptimizerImpl::ProcessLocation загружает данные о местоположении из предоставленного потока XML-данных. В этом методе XmlDocument::SelectSingleNodeNS используется для обработки предоставленного объекта XmlDocument. В данном примере показано, как метод TripOptimizerImpl::ProcessLocation извлекает код ответа для запроса.
// Move to response code.
// Report an error and return if the status code is
// not 200 (OK).
xmlNode = xmlDocument->SelectSingleNodeNS(L"/d:Response/d:StatusCode/text()", ns);
if (xmlNode == nullptr) throw ref new NullReferenceException("Failed to parse status code from HTTP response");
Важно!
Убедитесь, что при использовании метода XmlDocument::SelectSingleNodeNS для выбора текстовых узлов из XML-документа применяется нотация text().
Если возникает ошибка, метод TripOptimizerImpl::ProcessLocation вызывает исключение. В этом примере метод TripOptimizerImpl::ProcessLocation создает исключение Platform::NullReferenceException, если XML-документ не содержит ожидаемых данных. Поскольку эту ошибку невозможно устранить, компонент не перехватывает указанное исключение. Поэтому при возникновении исключения оно передается в обработчик ошибок основного приложения.
Метод TripOptimizerImpl::ProcessLocation считывает общее число ресурсов из потока XML. Ресурсом здесь называется возможное совпадение имени местоположения. Например, если указать "Pittsburgh", Bing Maps может возвратить следующие возможности: "Pittsburgh, PA", "Pittsburgh, ON" и "Pittsburgh, GA". Затем метод TripOptimizerImpl::ProcessLocation заполняет соответствующий объект Node для каждого ресурса, используя широту и долготу местоположения. Если возвращается несколько ресурсов, метод TripOptimizerImpl::ProcessLocation добавляет узел к переменной OptimizeTripParams::UnresolvedLocations.
// If there is only a single name, set it as the resolved name and
// location.
if (node->Names.size() == 1)
{
node->ResolvedLocation = node->Locations.front();
node->ResolvedName = node->Names.front();
}
// Otherwise, add the node to the list of unresolved locations.
else if (node->ResolvedName.length() == 0)
{
unresolvedLocations.push_back(node);
}
Если переменная OptimizeTripParams::UnresolvedLocations не содержит элементов, метод TripOptimizerImpl::InternalOptimizeTripAsync переходит ко второму этапу, который состоит в извлечении данных о маршруте из Bing Maps.
Дополнительные сведения о службе расположений Bing Maps см. в разделе API расположений.
Этап 2. Получение данных о маршруте
На втором этапе метод TripOptimizerImpl::InternalOptimizeTripAsync получает данные о маршруте из службы Bing Maps. Данные о маршруте извлекаются потому, что алгоритму оптимизации маршрута требуется расстояние между каждым набором точек на графе. Не забудьте, что граница соединяет два узла на графе и каждый узел содержит местоположение.
//
// Phase 2: Retrieve route information for each pair of locations.
//
// Report progress.
reporter.report("Retrieving routes (0% complete)...");
auto tasks = RetrieveRoutesAsync(params, cancellationToken, reporter);
// Move to the next phase after all current tasks complete.
return when_all(begin(tasks), end(tasks)).then(
[=](task<void> t) -> task<IMap<String^, IVector<String^>^>^>
{
// The PPL requires that all exceptions be caught and handled.
// If any task in the collection errors, ensure that all errors in the entire
// collection are observed at least one time.
try
{
// Calling task.get will cause any exception that occurred
// during the task to be thrown.
t.get();
}
catch (Exception^)
{
observe_all_exceptions<void>(begin(tasks), end(tasks));
// Rethrow the original exception.
throw;
}
catch (const task_canceled&)
{
observe_all_exceptions<void>(begin(tasks), end(tasks));
// Rethrow the original exception.
throw;
}
// Report progress.
reporter.report("Retrieving routes (100% complete)...");
// Record the elapsed HTTP time.
params->HttpTime = GetTickCount64() - params->HttpTime;
Извлекая данные о маршруте из службы Bing Maps, метод TripOptimizerImpl::RetrieveRoutesAsync использует следует той же модели, что и метод TripOptimizerImpl::RetrieveLocationsAsync.
// Retrieves distance information for each pair of locations from the Bing Maps route service.
vector<task<void>> TripOptimizerImpl::RetrieveRoutesAsync(
shared_ptr<OptimizeTripParams> params,
cancellation_token cancellationToken,
progress_reporter<String^> reporter)
{
// Holds the tasks that process the returned XML documents.
vector<task<void>> tasks;
// Implementation note:
// We assume that the route from A -> B is the same *distance* as
// the route from B -> A. Although this enables us to make fewer HTTP route requests,
// the final route might be slightly sub-optimal if the trip contains legs with
// one-way streets or the distance from A -> B differs from the distance from B -> A.
// (However, the end route will always contain the correct turn information.)
// Create HTTP requests for route information.
auto edges = params->Edges;
params->RequestsPending = static_cast<long>(edges.size()); // Used to report progress.
for (auto edge : edges)
{
// Create request URL.
LatLong pointA = edge->PointA->ResolvedLocation;
LatLong pointB = edge->PointB->ResolvedLocation;
wstringstream uri;
uri << L"http://dev.virtualearth.net/REST/v1/Routes/" << params->TravelMode
<< L"?wp.0=" << pointA.Latitude << L"," << pointA.Longitude
<< L"&wp.1=" << pointB.Latitude << L"," << pointB.Longitude
<< L"&optmz=" << params->Optimize
<< L"&o=xml"
<< L"&key=" << params->BingMapsKey;
// Create a parent task that downloads the XML document.
auto httpRequest = make_shared<HttpRequest>();
auto downloadTask = httpRequest->GetAsync(
ref new Uri(ref new String(uri.str().c_str())),
cancellationToken);
// Create a continuation task that fills route information after
// the download finishes.
tasks.push_back(downloadTask.then([=](task<wstring> previousTask)
{
(void)httpRequest;
// Get the result to force exceptions to be thrown.
wstring response = previousTask.get();
try
{
// Create and load the XML document from the response.
XmlDocument^ xmlDocument = ref new XmlDocument();
auto xml = ref new String(response.c_str() + 1); // Bypass BOM.
xmlDocument->LoadXml(xml);
// Fill in route information.
ProcessRoute(edge, xmlDocument);
}
catch (Exception^)
{
// An error occurred. Cancel any other active downloads.
m_cancellationTokenSource.cancel();
// Rethrow the exception.
throw;
}
// Report progress.
wstringstream progress;
progress << L"Retrieving routes ("
<< static_cast<long>(100.0 * (params->Edges.size() - params->RequestsPending) / params->Edges.size())
<< L"% complete)...";
reporter.report(ref new String(progress.str().c_str()));
InterlockedDecrement(¶ms->RequestsPending);
}));
}
return tasks;
}
Загружая данные, метод TripOptimizerImpl::ProcessRoute следует модели, аналогичной методу TripOptimizerImpl::ProcessLocation. Различие состоит в том, что метод TripOptimizerImpl::ProcessRoute загружает данные о маршрута в объект Edge для пары местоположений.
//
// Update edges.
// Set travel distance.
edge->TravelDistance = travelDistance;
// Ensure that the distance is at least FLT_EPSILON.
// If the travel distance is very short, a value below FLT_EPSILON
// can result in a divide by zero error in the trip optimization algorithm.
if (edge->TravelDistance < FLT_EPSILON)
{
edge->TravelDistance = FLT_EPSILON;
}
// Set latitude and longitude of both points.
edge->PointA->ResolvedLocation = LatLong(lat0, lon0);
edge->PointB->ResolvedLocation = LatLong(lat1, lon1);
После того как метод TripOptimizerImpl::RetrieveRoutesAsync обработает все данные о маршруте, метод TripOptimizerImpl::InternalOptimizeTripAsync выполняет конечный этап, который заключается в оптимизации маршрута.
Дополнительные сведения о службе маршрутов Bing Maps см. в разделе API маршрутов.
Этап 3. Вычисление оптимизированного маршрута
На третьем этапе метод TripOptimizerImpl::InternalOptimizeTripAsync выполняет оптимизацию маршрута для поиска наиболее короткого расстояния пути. Он вызывает функцию AntSystem::OptimizeRoute, которая вычисляет оптимизированный маршрут, используя узлы и границы, а также другие параметры, такие как входные данные для алгоритма оптимизации "колония муравьев".
// Run the simulation.
vector<size_t> routeIndices = OptimizeRoute(
params->Nodes, params->Edges,
params->Alpha, params->Beta, params->Rho,
params->Iterations,
cancellationToken,
&progressCallback,
params->Parallel);
После завершения работы функции AntSystem::OptimizeRoute метод TripOptimizerImpl::InternalOptimizeTripAsync изменяет порядок в соответствии с входными данными пользователя. Иначе говоря, он делает первую запись пользователя первой записью в оптимизированном маршруте.
// Create the final route.
// The optimizer returns a route that has an arbitrary starting point.
// For example, the route might look like:
// A -> B -> C -> D -> E -> A
// If our starting point was D, we want the route to look like:
// D -> E -> A -> B -> C -> D
routeIndices.pop_back();
while (routeIndices.front() != 0)
{
routeIndices.push_back(routeIndices.front());
routeIndices.erase(begin(routeIndices));
}
routeIndices.push_back(routeIndices.front());
Затем метод TripOptimizerImpl::InternalOptimizeTripAsync создает параллельные векторы, которые включают данные о местоположениях (широту и долготу) и отображаемые имена. Эти векторы содержатся в объекте Platform::Collections::Map. (Map является реализацией C++ для интерфейса Windows::Foundation::Collections::IMap<K, V>. Аналогичным образом, Platform::Collections::Vector — это реализация C++ для интерфейса Windows::Foundation::Collections::IVector<T>.) Основное приложение использует данные о местоположениях для отображения карты и имен местоположений в составе пошаговых инструкций движения.
//
// Prepare the return value.
//
// Parallel arrays that hold the optimized route locations and names.
IVector<String^>^ optimizedRoute; // {"47.620056,-122.349261", ...}
IVector<String^>^ optimizedRouteDisplayNames; // {"Space Needle, WA", ...}
optimizedRoute = ref new Vector<String^>();
optimizedRouteDisplayNames = ref new Vector<String^>();
// Fill the arrays.
size_t i = 0;
for (size_t index : routeIndices)
{
const auto node = params->Nodes[index];
String^ v;
// The location is the latitude and longitude of the waypoint.
// For example, "47.620056,-122.349261"
wstringstream location;
location << node->ResolvedLocation.Latitude << L',' << node->ResolvedLocation.Longitude;
v = ref new String(location.str().c_str());
optimizedRoute->InsertAt(static_cast<unsigned int>(i), v);
// The display name if the resolved name of the waypoint.
// For example, "Space Needle, WA"
v = ref new String(node->ResolvedName.c_str());
optimizedRouteDisplayNames->InsertAt(static_cast<unsigned int>(i), v);
++i;
}
// The return value.
auto finalRoute = ref new Map<String^, IVector<String^>^>();
finalRoute->Insert("locations", optimizedRoute);
finalRoute->Insert("displayNames", optimizedRouteDisplayNames);
// Compute the overall elapsed time.
params->TotalTime = GetTickCount64() - params->TotalTime;
// Report final progress.
// This message contains the overall elapsed time, the time spent performing
// HTTP requests, and the time it took to run the simulation.
wstringstream progress;
progress << L"Loading Map. Elapsed time: "
<< params->TotalTime << L"ms (total); "
<< params->HttpTime << L"ms (HTTP); "
<< params->SimulationTime << L"ms (simulation).";
reporter.report(ref new String(progress.str().c_str()));
return task_from_result<IMap<String^, IVector<String^>^>^>(finalRoute);
[Наверх]
Определение функциональности HTTP
Компонент C++ определяет класс HttpRequest для обработки HTTP-запросов. В этом классе для обработки HTTP-запросов используется интерфейс IXMLHTTPRequest2. Интерфейс IXMLHTTPRequest2 поддерживает только асинхронные операции. Чтобы упростить использование этих асинхронных операций вызывающим объектом, метод HttpRequest::GetAsync возвращает объект concurrency::task<std::wstring>. Этот объект задачи содержит ответ HTTP в виде строки.
Примечание
IXMLHTTPRequest2 является одним из способов подключения к веб-службе.Сведения о других способах подключения к службам в приложении см. в разделе Подключение к веб-службам.
Поскольку интерфейс IXMLHTTPRequest2 поддерживает только асинхронные операции, при запросе данных с HTTP-сервера необходимо предоставить объект IXMLHTTPRequest2Callback. Файл HttpRequest.cpp определяет класс HttpRequestStringCallback, который наследуется от этого интерфейса и реализует его методы.
Важной частью этой реализации является использование класса concurrency::task_completion_event. Этот класс позволяет классу HttpReader создать задачу, которая устанавливается после завершения другой асинхронной задачи. Данный класс удобно использовать при создании объектов task вместе с асинхронными операциями, которые выполняются через обратные вызовы. После успешного завершения операции загрузки метод HttpRequestStringCallback::OnResponseReceived задает событие завершения.
completionEvent.set(make_tuple<HRESULT, wstring>(move(hr), move(wstr)));
Соответственно, метод HttpRequestStringCallback::OnError задает событие завершения при возникновении ошибки. В этом случае результатом задачи являются код ошибки и пустая строка.
completionEvent.set(make_tuple<HRESULT, wstring>(move(hrError), wstring()));
Метод HttpRequest::GetAsync вызывает HttpRequest::DownloadAsync. Метод HttpRequest::DownloadAsync открывает асинхронный запрос и создает объект HttpRequestStringCallback. Затем он создает объект task, который завершается при завершении события завершения задачи объекта HttpRequestStringCallback. Этот объект task с помощью продолжения освобождает объект HttpRequestStringCallback после завершения события завершения задачи.
// Start a download of the specified URI using the specified method. The returned task produces the
// HTTP response text. The status code and reason can be read with GetStatusCode() and GetReasonPhrase().
task<wstring> HttpRequest::DownloadAsync(PCWSTR httpMethod, PCWSTR uri, cancellation_token cancellationToken,
PCWSTR contentType, IStream* postStream, uint64 postStreamSizeToSend)
{
// Create an IXMLHTTPRequest2 object.
ComPtr<IXMLHTTPRequest2> xhr;
CheckHResult(CoCreateInstance(CLSID_XmlHttpRequest, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&xhr)));
// Create callback.
auto stringCallback = Make<HttpRequestStringCallback>(xhr.Get(), cancellationToken);
CheckHResult(stringCallback ? S_OK : E_OUTOFMEMORY);
auto completionTask = create_task(stringCallback->GetCompletionEvent());
// Create a request.
CheckHResult(xhr->Open(httpMethod, uri, stringCallback.Get(), nullptr, nullptr, nullptr, nullptr));
if (postStream != nullptr && contentType != nullptr)
{
CheckHResult(xhr->SetRequestHeader(L"Content-Type", contentType));
}
// Send the request.
CheckHResult(xhr->Send(postStream, postStreamSizeToSend));
// Return a task that completes when the HTTP operation completes.
// We pass the callback to the continuation because the lifetime of the
// callback must exceed the operation to ensure that cancellation
// works correctly.
return completionTask.then([this, stringCallback](tuple<HRESULT, wstring> resultTuple)
{
// If the GET operation failed, throw an Exception.
CheckHResult(std::get<0>(resultTuple));
statusCode = stringCallback->GetStatusCode();
reasonPhrase = stringCallback->GetReasonPhrase();
return std::get<1>(resultTuple);
});
}
Сведения о том, как класс TripOptimizerImpl использует результирующие XML-данные для вычисления оптимизированного маршрута, см. в разделе Рабочий процесс компонента в этом документе. Другие примеры использования интерфейса IXMLHTTPRequest2 см. в разделах Quickstart: Connecting using XML HTTP Request и Пошаговое руководство. Подключение с использованием задач и HTTP-запросов XML.
[Наверх]
Вычисление оптимального маршрута
Основной алгоритм, который выполняет вычисление маршрута, определен в файлах AntSystem.h и AntSystem.cpp. Вычисление маршрута напоминает задачу о пути коммивояжера. Цель задачи пути коммивояжера состоит в том, чтобы на основе коллекции расположений и расстояний между каждой парой расположений вычислить кратчайший маршрут, в котором каждое местоположение посещается только один раз. Задачу пути коммивояжера, как правило, сложно решить, поскольку для поиска оптимального маршрута в ней необходимо вычислить каждый возможный маршрут. Алгоритм оптимизации "колония муравьев" — это метаэвристический подход к решению такого рода задач. Он моделирует поведение муравьев для быстрого поиска оптимизированного маршрута. Хотя маршруты, найденные с помощью этого алгоритма, не обязательно будут самыми короткими для данного набора местоположений, ему часто удается найти кратчайший маршрут или маршрут, достаточно короткий для целей конкретного путешествия.
Файлы AntSystem.h и AntSystem.cpp определяют пространство имен AntSystem. Это пространство имен не содержит зависимостей от Среда выполнения Windows и поэтому не использует C++/CX. AntSystem.h определяет структуры LatLong, Node и Edge. Он также определяет функцию OptimizeRoute.
Структура LatLong представляет широту и долготу точки на карте.
// Represents the latitude and longitude of a single point on a map.
struct LatLong
{
explicit LatLong(double latitude, double longitude)
: Latitude(latitude)
, Longitude(longitude)
{
}
// The coordinates of the location.
double Latitude;
double Longitude;
};
Структура Node представляет узел в графе. Она содержит имя местоположения, а также его широту и долготу. Она также содержит все альтернативные имена, полученные из службы Bing Maps.
// Represents a node in a graph.
struct Node
{
explicit Node(const std::wstring& inputName)
: InputName(inputName)
, ResolvedLocation(0.0, 0.0)
{
}
// The name of the location as provided by the user.
std::wstring InputName;
// The resolved latitude and longitude of the location as provided by the
// Bing Maps location service.
LatLong ResolvedLocation;
// The resolved name of the location as provided by the
// Bing Maps location service.
std::wstring ResolvedName;
//
// Parallel arrays of string names and latitude, longitude pairs that represent
// all possible resolved locations for the current input name.
// For example, if the input name is "Redmond", the Names array might contain
// "Redmond, WA", "Redmond, OR", "Redmond, UT", and "Redmond, Australia".
// The Locations array would contain the corresponding latitude and longitude
// for each location.
std::vector<std::wstring> Names;
std::vector<LatLong> Locations;
};
Структура Edge соединяет два узла и содержит расстояние между ними. Она также содержит данные, используемые алгоритмом для оптимизации "колония муравьев".
// Represents an edge in a graph of Node objects.
// An Edge object connects two nodes and holds the travel distance between
// those nodes. An Edge object also holds the amount of pheromone that
// exists on that edge.
struct Edge
{
explicit Edge(std::shared_ptr<Node> pointA, std::shared_ptr<Node> pointB)
: PointA(pointA)
, PointB(pointB)
, Pheromone(0.0)
, TravelDistance(-1.0)
{
}
// The start node.
std::shared_ptr<Node> PointA;
// The end node.
std::shared_ptr<Node> PointB;
// The amount of pheromone on the edge.
double Pheromone;
// The distance from the start node to the end node.
double TravelDistance;
};
Компонент C++ создает объект Node для каждого местоположения в маршруте и объект Edge для каждой пары местоположений. Собрав все необходимые данные из веб-служб Bing Maps, компонент вызывает функцию OptimizeRoute для вычисления оптимального маршрута.
// Optimizes the route among the given nodes for the shortest travel distance.
// This method returns indicies to the provided Node collection.
std::vector<size_t> OptimizeRoute(
std::vector<std::shared_ptr<Node>>& nodes,
std::vector<std::shared_ptr<Edge>>& edges,
double alpha,
double beta,
double rho,
unsigned int iterations,
Concurrency::cancellation_token cancellationToken,
std::function<void(unsigned int)>* progressCallback = nullptr,
bool parallel = true);
Для краткости в этой документации не приведено подробное описание алгоритма оптимизации "колония муравьев". Дополнительные сведения см. в файле AntSystem.cpp в исходном коде.
Однако одним важным аспектом реализации этого алгоритма является использование параллелизма. Алгоритм оптимизации "колония муравьев" выполняет несколько итераций трех основных шагов: свободное перемещение каждого муравья по графу, распыление феромона и возвращение каждого муравья к начальной точке по своим следам. Операции первого шага — свободного перемещения каждого муравья по графу — могут выполняться параллельно, поскольку каждый муравей действует независимо. Этот шаг не содержит общих данных или зависимых вычислений.
// Perform the simulation several times.
auto startTime = GetTickCount64();
for (unsigned int i = 0; i < iterations; ++i)
{
// Occasionally check for cancellation.
auto time = GetTickCount64();
if (time - startTime > 100)
{
if (cancellationToken.is_canceled())
{
// Return the empty collection.
return vector<size_t>();
}
startTime = time;
}
// Send progress.
if (progressCallback != nullptr)
{
(*progressCallback)(i);
}
//
// Allow each ant to perform a tour of the graph.
// Note that this operation can be performed in parallel because each ant acts independently.
// This step contains no shared data or dependent computations.
if (parallel)
{
parallel_for_each(begin(ants), end(ants), [&](Ant& blitz)
{
blitz.Explore();
});
}
else
{
for_each(begin(ants), end(ants), [&](Ant& blitz)
{
blitz.Explore();
});
}
//
// Evaporate pheromone.
for_each(begin(edges), end(edges), [rho](shared_ptr<Edge> edge)
{
edge->Pheromone *= (1.0 - rho);
});
//
// Allow each ant to backtrack through the graph and drop pheromone on
// each edge.
//
// Note that this operation is NOT performed in parallel because
// the ants update the pherone value of each edge.
// Because the backtrack operation is not relatively lengthy, the
// overhead that is required to synchronize access to the edges would
// likely outweigh any benefits of parallel processing.
for_each(begin(ants), end(ants), [&](Ant& blitz)
{
blitz.Backtrack();
});
}
Дополнительные сведения об алгоритмах параллельных операций, таких как parallel_for_each, см. в разделе Параллельные алгоритмы.
Примечание
Использование флага parallel не является обязательной частью реализации.Он предоставляется в качестве параметра в пользовательском интерфейсе, чтобы можно было легко экспериментировать с параллельными вычислениями и сравнивать их с последовательными вычислениями.
[Наверх]
Обработка отмены
Каждый из интерфейсов IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> и IAsyncOperationWithProgress<TResult, TProgress> предоставляет метод Cancel, который позволяет отменить асинхронную операцию. В C++ эти методы Cancel отменяют токен отмены, связанный с асинхронной операцией. Связь отмены задачи с методами Среда выполнения Windows Cancel можно осуществить двумя способами. Во-первых, можно указать рабочую функцию, которая передается в функцию create_async, чтобы принять объект concurrency::cancellation_token. При вызове метода Cancel токен отмены отменяется и применяются обычные правила отмены. Если объект cancellation_token не предоставлен, он определяется неявно базовым объектом task. Если необходимо совместно реагировать на отмену в рабочей функции, определите объект cancellation_token. Дополнительные сведения о механизме отмены см. в разделе Создание асинхронных операций в C++ для приложений для Магазина Windows.
Отмена происходит, когда пользователь нажимает кнопку Cancel (Отмена) в приложении JavaScript или возникает неустранимая ошибка. Необходимо предоставить пользователю возможность отменить операцию, поэтому очень важно, чтобы пользовательский интерфейс продолжал отвечать на запросы во время оптимизации. Отмена не происходит немедленно. Компонент C++ использует функции concurrency::cancellation_token_source и concurrency::cancellation_token, чтобы отправлять сигнал отмены и периодически проверять наличие отмены. Компонент C++ реализует отмену на наименее детализированном уровне, но, однако, стремится обеспечить своевременное выполнение отмены. С точки зрения производительности, слишком частая проверка наличия отмены отрицательно скажется на работе приложения. В действительности производительность может снизиться, если время, затрачиваемое на проверку отмены, превысит время выполнения задачи.
Компонент C++ проверяет выполнение отмены двумя различными способами. Во-первых, задача продолжения, которая выполняется после каждого этапа оптимизации, вызывает concurrency::task::get для проверки отмены. Метод task::get создает любое исключение, которое произошло во время задачи, включая task_canceled если произошла отмена. (В случае when_all среда выполнения выбирает одно из исключений, если создано несколько задач.) Поскольку необходимо контролировать все возникающие исключения задач, определяется функция observe_all_exceptions, контролирующая все исключения, которые возникают в задачах, предоставляемых алгоритму when_all. В следующем примере показана проверка отмены после извлечения местоположений из службы Bing Maps, но перед извлечением маршрутов.
//
// Phase 2: Retrieve route information for each pair of locations.
//
// Report progress.
reporter.report("Retrieving routes (0% complete)...");
auto tasks = RetrieveRoutesAsync(params, cancellationToken, reporter);
// Move to the next phase after all current tasks complete.
return when_all(begin(tasks), end(tasks)).then(
[=](task<void> t) -> task<IMap<String^, IVector<String^>^>^>
{
// The PPL requires that all exceptions be caught and handled.
// If any task in the collection errors, ensure that all errors in the entire
// collection are observed at least one time.
try
{
// Calling task.get will cause any exception that occurred
// during the task to be thrown.
t.get();
}
catch (Exception^)
{
observe_all_exceptions<void>(begin(tasks), end(tasks));
// Rethrow the original exception.
throw;
}
catch (const task_canceled&)
{
observe_all_exceptions<void>(begin(tasks), end(tasks));
// Rethrow the original exception.
throw;
}
// Report progress.
reporter.report("Retrieving routes (100% complete)...");
// Record the elapsed HTTP time.
params->HttpTime = GetTickCount64() - params->HttpTime;
После вызова метода observe_all_exceptions снова создается исходное исключение, чтобы оно могло быть обработано кодом, который зависит от задачи.
Ниже показана функция observe_all_exceptions. Она выполняет перебор всех объектов task в предоставленной коллекции и использует метод task::get для проверки на наличие ошибок. Так как мы планируем в будущем заново создать одно из исключений, мы используем пустые блоки catch, чтобы указать, что исключение было обнаружено и обработано.
// 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)
{
for_each (first, last, [](task<T> t)
{
t.then([](task<T> previousTask)
{
try
{
previousTask.get();
}
catch (Exception^)
{
// Swallow the exception.
}
catch (const exception&)
{
// Swallow the exception.
}
});
});
}
Второй способ проверки отмены компонентом заключается в вызове метода concurrency::cancellation_token::is_canceled. Алгоритм оптимизации маршрута (функция AntSystem::OptimizeRoute) проверяет наличие отмены таким образом каждые 100 миллисекунд.
// Perform the simulation several times.
auto startTime = GetTickCount64();
for (unsigned int i = 0; i < iterations; ++i)
{
// Occasionally check for cancellation.
auto time = GetTickCount64();
if (time - startTime > 100)
{
if (cancellationToken.is_canceled())
{
// Return the empty collection.
return vector<size_t>();
}
startTime = time;
}
Примечание
Можно было бы использовать первый метод, то есть вызов функций is_task_cancellation_requested и cancel_current_task, вместо вызова метода cancellation_token::is_canceled.Однако функция cancel_current_task должна вызываться из объекта task.Поскольку теоретически можно использовать эту реализацию для вызова функции AntSystem::OptimizeRoute из task или из другой части кода, рекомендуется использовать токены отмены напрямую для большей гибкости.Если эта функция должна вызываться из кода, который не использует задачи, можно передать токен concurrency::cancellation_token::none для параметра cancellationToken.Токен none нельзя отменить.
В разделе Определение функциональности HTTP описывается, как класс HttpRequestStringCallback использует событие task_completion_event для создания асинхронных операций, которые выполняются через обратные вызовы вместе с объектами task. Аналогичным образом, для поддержки отмены класс HttpRequestStringCallback использует метод concurrency::cancellation_token::register_callback, чтобы зарегистрировать функцию обратного вызова, которая вызывается при отмене токена отмены. Этот метод полезен, поскольку интерфейс IXMLHTTPRequest2 выполняет асинхронную работу, которую мы не можем контролировать. После отмены токена отмены функция обратного вызова прерывает HTTP-запрос и задает событие завершения задачи.
HttpRequestStringCallback(IXMLHTTPRequest2* r, cancellation_token ct) :
request(r), cancellationToken(ct)
{
// Register a callback function that aborts the HTTP operation when
// the cancellation token is canceled.
if (cancellationToken != cancellation_token::none())
{
registrationToken = cancellationToken.register_callback([this]()
{
if (request != nullptr)
{
request->Abort();
}
});
}
}
Метод cancellation_token::register_callback возвращает объект concurrency::cancellation_token_registration, который определяет регистрацию обратного вызова. Деструктор класса HttpRequest использует этот объект регистрации для отмены регистрации функция обратного вызова. Рекомендуется всегда отменять регистрацию обратного вызова, если в нем больше нет необходимости, чтобы обеспечить допустимость всех объектов при вызове функции обратного вызова.
~HttpRequestStringCallback()
{
// Unregister the callback.
if (cancellationToken != cancellation_token::none())
{
cancellationToken.deregister_callback(registrationToken);
}
}
В случае неустранимой ошибки все оставшиеся задачи отменяются. Например, если не удается обработать XML-документ, общая операция отменяется и снова вызывается исключение.
try
{
// Create and load the XML document from the response.
XmlDocument^ xmlDocument = ref new XmlDocument();
auto xml = ref new String(response.c_str() + 1); // Bypass BOM.
xmlDocument->LoadXml(xml);
// Fill in location information.
ProcessLocation(node, xmlDocument, params->UnresolvedLocations);
}
catch (Exception^)
{
// An error occurred. Cancel any active operations.
m_cancellationTokenSource.cancel();
// Rethrow the exception.
throw;
}
Класс TripOptimizerImpl определяет объект concurrency::cancellation_token_source, поскольку отмена инициируется через этот класс. Чтобы обеспечить возможность отмены задачи как с помощью кнопки Cancel (Отмена), так и посредством внутреннего кода, класс TripOptimizerImpl вызывает метод concurrency::cancellation_token_source::create_linked_source. Этот связанный источник токена отмены позволяет и приложению JavaScript, и классу TripOptimizerImpl отменить один и тот же токен отмены, но из различных объектов cancellation_token_source.
// Perform the operation asynchronously.
return create_async([this, params](progress_reporter<String^> reporter,
cancellation_token cancellationToken) -> task<IMap<String^, IVector<String^>^>^>
{
// Create a linked source for cancellation.
// This enables both the caller (through the returned
// IAsyncOperationWithProgress object) and this class to set
// the same cancellation token.
m_cancellationTokenSource =
cancellation_token_source::create_linked_source(cancellationToken);
Дополнительные сведения о том, как работает отмена в библиотеке параллельных шаблонов, см. в разделе Отмена в библиотеке параллельных шаблонов.
[Наверх]
Переход с версии ActiveX
Сведения о переходе с версии ActiveX приложения Bing Maps Trip Optimizer к приложению Магазин Windows см. в разделе Перенос существующего кода в примере Bing Maps Trip Optimizer.
[Наверх]
Следующие шаги
Сведения о том, как компонент C++ Среда выполнения Windows взаимодействует с компонентом JavaScript, см. в разделе Взаимодействие кода JavaScript и C++ в примере Bing Maps Trip Optimizer.
[Наверх]
См. также
Основные понятия
Взаимодействие кода JavaScript и C++ в примере Bing Maps Trip Optimizer
Использование JavaScript в примере Bing Maps Trip Optimizer
Другие ресурсы
Разработка Bing Maps Trip Optimizer — приложения для Магазина Windows — с помощью JavaScript и C++