Usando C++ no exemplo do Bing Maps Trip Optimizer
Este documento descreve o componente C++ do Bing Maps Trip Optimizer. O componente C++ usa código nativo puro para implementar a lógica da otimização de rota e de código Extensões de componentes Visual C++ (C++/CX) para fazer a interface com outros componentes do Tempo de Execução do Windows.
Dica
Para obter mais informações sobre C++/CX, as extensões de linguagem disponíveis para um aplicativo Windows Store gravado em C++, consulte Referência da linguagem Visual C++ (C++/CX).
Para obter informações sobre como criar um componente em C++ básico do Tempo de Execução do Windows, consulte Instruções passo a passo: criando um componente de Tempo de Execução do Windows básico em C++ e chamando-o em JavaScript ou C#.
A parte C++ do aplicativo é escrita como uma DLL (biblioteca de vínculo dinâmico) do Tempo de Execução do Windows. Essa biblioteca fornece esta funcionalidade:
Recuperar a latitude e a longitude de cada local do serviço Web do Bing Maps. Essa operação usa a interface REST (Representational State Transfer) do Bing Maps.
Recuperar a distância do trajeto entre cada par possível de pontos na viagem. Essa operação também usa a interface REST do Bing Maps.
Executar a otimização da rota. Essa operação usa o algoritmo de otimização com colônia de formigas e o processamento paralelo para calcular, com eficiência, a rota otimizada.
Essas operações são executadas de forma assíncrona ou no plano de fundo. Isso permite à interface do usuário continuar responsiva durante o processo de otimização.
Aqui estão alguns dos principais recursos e tecnologias que o componente de C++ usa:
concurrency::create_async para criar um objeto Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>. O aplicativo JavaScript usa esse objeto para monitorar o andamento e a conclusão e cancelar a tarefa de otimização. O componente usa concurrency::task para se comunicar com o Bing Maps e executar a tarefa de otimização no plano de fundo.
Serviços REST do Bing Maps para recuperar informações de local e rota.
IXMLHTTPRequest2 para solicitar e ler de forma assíncrona fluxos HTTP do serviço REST do Bing Maps.
Windows::Data::Xml::Dom::XmlDocument para ler respostas do serviço Web do Bing Maps.
O algoritmo de concurrency::parallel_for_each para efetuar a paralelização do algoritmo de otimização de rota.
Dica
O código de exemplo que corresponde a este documento é encontrado no Exemplo Bing Maps Trip Optimizer.
Neste documento
Convenções de código
Organização do arquivo
Criando as classes TripOptimizer e TripOptimizerImpl
Fluxo de trabalho do componente
Etapa 1: Recuperando dados de local
Etapa 2: Recuperando dados de rota
Etapa 3: Calculando a rota otimizada
Definindo a funcionalidade HTTP
Calculando a rota ideal
Processando o cancelamento
Migração do ActiveX
Próximas etapas
Convenções de código
Além de usar o Tempo de Execução do Windows para criar o componente Windows Store, o componente C++ usa convenções de código modernas, como ponteiros inteligentes e manipulação de exceção.
Tempo de Execução do Windows é uma interface de programação que você pode usar para criar aplicativos Windows Store executados somente em um ambiente de sistema operacional confiável. Esses aplicativos usam funções autorizadas, tipos de dados e dispositivos, além de serem distribuídos a partir da Windows Store. O Tempo de Execução do Windows é representado pela Interface Binária de Aplicativo (ABI). ABI é um contrato binário subjacente que torna as APIs do Tempo de Execução do Windows disponíveis para as linguagens de programação como Visual C++.
Para facilitar a gravação de aplicativos que usam o Tempo de Execução do Windows, a Microsoft fornece as extensões de linguagem ao Visual C++ que oferecem suporte especificamente ao Tempo de Execução do Windows. Muitas dessas extensões de linguagem são semelhantes à sintaxe para a linguagem do C++/CLI. No entanto, em vez de direcionar o Common Language Runtime (CLR), os aplicativos nativos usam essa sintaxe para direcionar o Tempo de Execução do Windows. O modificador de chapéu (^) é uma parte importante dessa nova sintaxe porque ele possibilita a exclusão automática de objetos de tempo de execução por meio de contagem de referência. Em vez de chamar métodos como AddRef e Release para gerenciar o tempo de vida de um objeto do Tempo de Execução do Windows, o tempo de execução exclui o objeto quando nenhum outro componente o referencia, por exemplo, quando ele sai do escopo ou quando você define todas as referências a nullptr. Outra parte importante do Visual C++ para criar aplicativos Windows Store é a palavra-chave ref new. Use ref new em vez de new para criar objetos do Tempo de Execução do Windows contados para referência. Para obter mais informações, consulte Objetos do Tempo de Execução do Windows e ref new.
Importante
Você só precisa usar ^ e ref new ao criar objetos do Tempo de Execução do Windows ou componentes Tempo de Execução do Windows.Você pode usar a sintaxe de C++ padrão ao gravar o código do aplicativo principal que não usa o Tempo de Execução do Windows.
Dica
O Bing Maps Trip Optimizer usa ^ juntamente com Microsoft::WRL::ComPtr, std::shared_ptr e std::unique_ptr para gerenciar objetos alocados no heap e minimizar vazamentos de memória.Recomendamos que você use ^ para gerenciar o tempo de vida de variáveis do Tempo de Execução do Windows, Microsoft::WRL::ComPtr para gerenciar o tempo de vida de variáveis COM, e shared_ptr ou unique_ptr para gerenciar o tempo de vida de todos os outros objetos C++ atribuídos no heap.
Para obter mais informações sobre programação C++ moderna, consulte Bem-vindo ao C++ (C++ moderno). Para obter mais informações sobre as extensões de linguagem disponíveis para um aplicativo C++ Windows Store, consulte Referência da linguagem Visual C++ (C++/CX).
[Superior]
Organização do arquivo
A lista a seguir descreve resumidamente a função de cada arquivo de código-fonte que faz parte do componente C++.
AntSystem.h, AntSystem.cpp
Define o algoritmo de otimização com colônia de formigas e suas estruturas de dados de suporte.HttpRequest.h, HttpRequest.cpp
Define a classe HttpRequest, que é uma classe auxiliar para executar solicitações HTTP assíncronas.pch.h, pch.cpp
O cabeçalho pré-compilado para o projeto.TripOptimizer.h, TripOptimizer.cpp
Define a classe TripOptimizer, que serve como a interface entre o aplicativo e a lógica do componente principal.TripOptimizerImpl.h, TripOptimizerImpl.cpp
Define a classe TripOptimizerImpl, que define a lógica do núcleo do componente.
[Superior]
Criando as classes TripOptimizer e TripOptimizerImpl
O componente C++ contém uma classe do Tempo de Execução do Windows, TripOptimizerComponent::TripOptimizer, que pode fazer a interface com outros componentes do Tempo de Execução do Windows. No Bing Maps Trip Optimizer, essa classe faz interface com a parte de JavaScript do aplicativo. (Como o componente C++ é gravado como uma DLL, você também pode usá-lo com outros aplicativos.) A classe TripOptimizer define apenas os métodos que se comunicam com outros componentes do Tempo de Execução do Windows. Os detalhes de implementação são tratados pela classe TripOptimizerImpl. Escolhemos esse padrão para melhor encapsular a interface pública e separá-la dos detalhes de implementação. Para obter mais informações sobre esse padrão, consulte Pimpl para encapsulamento do tempo de compilação (C++ moderno).
// 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;
};
Importante
Quando você cria uma classe de componente do Tempo de Execução do Windows que pode ser compartilhado com outros componentes externos, certifique-se de usar as palavras-chave public e sealed.Para obter mais informações sobre como criar um componente do Tempo de Execução do Windows em C++ que possa ser chamado de um aplicativo da Windows Store, consulte Criando componentes do Tempo de Execução do Windows em C++.
O método TripOptimizer::OptimizeTripAsync é como um aplicativo se comunica com o componente C++. Esse método inicia a sequência das tarefas que calculam o processamento otimizado. O chamador usa o valor de retorno para monitorar o andamento e a conclusão da tarefa de otimização. Também é usado para cancelar a operação. O cancelamento é explicado na seção Processando o cancelamento posteriormente neste documento.
O método TripOptimizer::OptimizeTripAsync transfere para a classe TripOptimizerImpl a execução de sua ação. TripOptimizerImpl é descrito com mais detalhes mais adiante neste documento.
// 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);
}
[Superior]
Fluxo de trabalho do componente
O método TripOptimizer::OptimizeTripAsync inicia a sequência de operações que calculam o processamento otimizado. Esse método se comporta de forma assíncrona para permitir que aplicativo continue responsivo. Para habilitar o comportamento assíncrono, esse método retorna Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>. Um componente do Tempo de Execução do Windows que chama esse método pode usar esse objeto para obter o valor de retorno quando está disponível. Essa interface também permite que o chamador monitore o andamento da operação e receba os erros ocorridos.
Dica
Como uma prática recomendada, o nome de um método assíncrono deve terminar com Async e o método deve retornar Windows::Foundation::IAsyncAction, Windows::Foundation::IAsyncActionWithProgress<TProgress>, Windows::Foundation::IAsyncOperation<TResult> ou Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>.
Cada linguagem ativada para Tempo de Execução do Windows (C++, JavaScript, etc.) prescreve sua própria maneira de criar uma operação assíncrona. Em C++, você pode usar a função concurrency::create_async. Essa função retorna um objeto IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> ou IAsyncOperationWithProgress<TResult, TProgress>. O tipo de retorno depende da assinatura do objeto de função transmitido. Por exemplo, como o método TripOptimizerImpl::InternalOptimizeTripAsync usa um objeto concurrency::progress_reporter como seu parâmetro e retorna um valor não void, create_async retorna IAsyncOperationWithProgress<TResult, TProgress>. Se esse método retornasse voidcreate_async retornaria IAsyncActionWithProgress<TProgress>. Para obter mais informações sobre create_async e como ele trabalha com o Windows 8, consulte Criando operações assíncronas n C++ para aplicativos da Windows Store.
O código a seguir mostra o método 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;
}
});
});
}
O objeto progress_reporter transmite mensagens de andamento ao chamador. O objeto cancellation_token permite que o componente responda a solicitações de cancelamento. (O cancelamento é descrito na seção Processando o cancelamento neste documento.) Para obter mais informações sobre como parte de JavaScript do aplicativo funciona com essa operação assíncrona que o TripOptimizer::OptimizeTripAsync retorna, consulte Interoperando entre JavaScript e C++ no exemplo do Bing Maps Trip Optimizer.
A função de trabalho fornecida para create_async em TripOptimizer::OptimizeTripAsync retorna um objeto task. você pode retornar um valor, T, ou uma tarefa, task<T>, de create_async. Nós retornamos task<T> para não aguardarmos o resultado das tarefas em segundo plano. Em vez de isso, deixamos que o tempo de execução recupere o resultado quando está disponível e transmitir o resultado ao chamador. Recomendamos que você siga esse padrão quando possível.
O arquivo TripOptimizer.cpp define a função auxiliar task_from_result que retorna um objeto task que é concluído imediatamente com o resultado fornecido. Essa função é útil quando você grava uma função que retorna task e essa função é retornada antecipadamente com um resultado específico.
// 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; });
}
A ilustração a seguir mostra o fluxo das operações que ocorrem quando um componente externo chama TripOptimizer::OptimizeTripAsync para iniciar o processo de otimização.
O método TripOptimizerImpl::OptimizeTripAsync chama o método TripOptimizerImpl::InternalOptimizeTripAsync como parte da operação assíncrona. O método TripOptimizerImpl::InternalOptimizeTripAsync chama TripOptimizerImpl::CreateGraph para criar um gráfico que representa o processamento. Cada local é representado por um nó, e cada par de nós é conectado por uma borda. Um nó contém informações sobre um local, como seu nome, sua latitude e sua longitude. Uma borda contém a distância do trajeto entre um par de nós.
// 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));
});
}
}
O método TripOptimizerImpl::InternalOptimizeTripAsync executa a otimização de processamento em três etapas. Na primeira etapa, esse método retorna dados local do Bing Maps. Na segunda etapa, esse método recupera as informações da rota entre cada par possível de pontos no processamento. Na terceira etapa, esse método executa o algoritmo de otimização de processamento. Cada etapa atua como uma continuação de tarefas da etapa anterior. As continuações permitem que você execute uma ou mais tarefas após o término da outra tarefa. As continuações serão descritas em mais detalhes neste documento. No entanto, como uma continuação é executada em segundo plano, é necessário armazenar variáveis que são compartilhadas entre tarefas de modo que as tarefas possam acessá-las posteriormente. A classe TripOptimizerImpl define a estrutura OptimizeTripParams. Essa estrutura contém as entradas para o método TripOptimizerImpl::InternalOptimizeTripAsync e as variáveis compartilhados entre as tarefas que compreendem a operação total.
// 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;
};
O método TripOptimizerImpl::OptimizeTripAsync cria uma estrutura OptimizeTripParams (usando um objeto std::shared_ptr) e a transmite a cada uma das continuações na cadeia de tarefas.
// 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;
Dica
Poderíamos criar essas variáveis como membros diretos da classe TripOptimizerImpl.No entanto, pelo fornecimento de uma estrutura de parâmetro, associamos mais claramente o estado de uma operação de otimização viagem com sua ação.Se usarmos variáveis de membro, poderá ser mais difícil oferecer suporte a recursos como a execução simultânea de várias otimizações de viagem.
Etapa 1: Recuperando dados de local
Na primeira etapa, o método TripOptimizerImpl::InternalOptimizeTripAsync recupera dados de local do Bing Maps. Os dados de local são recuperados de modo que o usuário possa confirmar que o Bing Maps têm a entrada correta. Por exemplo, se você especificar “Pittsburgh”, o Bing Maps não saberá se você quer dizer “Pittsburgh, PA”, “Pittsburgh, EM”, ou “Pittsburgh, GA”. Posteriormente, o componente C++ precisará recuperar a distância do trajeto entre cada par de locais. Portanto, se qualquer local for ambíguo, todos os possíveis locais deverão ser exibidos na interface do usuário para que o usuário possa selecionar um ou inserir um novo. A variável OptimizeTripParams::UnresolvedLocations rastreia locais não resolvidos. Se o método TripOptimizerImpl::RetrieveLocationsAsync preencher esse vetor com quaisquer valores, TripOptimizerImpl::InternalOptimizeTripAsync os retornará.
//
// 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);
}
O método TripOptimizerImpl::RetrieveLocationsAsync usa a classe concurrency::task para processar solicitações HTTP como tarefas em segundo plano. Como a classe HttpRequest funciona de forma assíncrona, TripOptimizerImpl::RetrieveLocationsAsync suspende todas as tarefas em segundo plano e usa a função concurrency::when_all para definir o trabalho que ocorre em seguida, após a conclusão delas. Como o método TripOptimizerImpl::RetrieveLocationsAsync faz parte da tarefa em segundo plano total, essa cadeia de tarefas de operações assíncronas não bloqueia o aplicativo principal.
Os detalhes sobre como funciona a classe HttpRequest serão explicados na seção Definindo a funcionalidade HTTP neste documento.
// 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;
}
O método TripOptimizerImpl::RetrieveLocationsAsync também usa o método concurrency::task::then para processar cada resposta do Bing Maps à medida que ele chega. O método task::then cria uma tarefa de continuação, que é uma tarefa executada após o término da tarefa anterior ou antecedente. A chamada para when_all no final do método TripOptimizerImpl::RetrieveLocationsAsync aguarda o término de todas as tarefas e suas tarefas de continuação. Para obter mais informações sobre as tarefas e continuações em C++, consulte Paralelismo de tarefas.
A API REST do Bing Maps retorna dados XML. O método TripOptimizerImpl::ProcessLocation carrega informações sobre um local do fluxo de dados XML fornecido. Esse método usa XmlDocument::SelectSingleNodeNS para processar o objeto XmlDocument fornecido. Esse exemplo mostra como o método TripOptimizerImpl::ProcessLocation recupera o código de resposta para a solicitação:
// 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");
Importante
Certifique-se de usar a notação text() quando usar XmlDocument::SelectSingleNodeNS para selecionar nós de texto de um documento XML.
Na ocorrência de erros, o método TripOptimizerImpl::ProcessLocation lança uma exceção. Nesse exemplo, TripOptimizerImpl::ProcessLocation gerará Platform::NullReferenceException se o documento XML não contiver os dados esperados. Como esse erro não é recuperável, o componente não captura essa exceção. Consequentemente, se ocorrer uma exceção, ela será transmitida ao manipulador de erros no aplicativo principal.
O método TripOptimizerImpl::ProcessLocation lê o número de recursos totais do fluxo XML. Recurso refere-se a uma correspondência possível para um nome de local. Por exemplo, se você especificar “Pittsburgh”, o Bing Maps talvez retorne “Pittsburgh, PA”, “Pittsburgh, ON” e “Pittsburgh, GA” como as possibilidades. Em seguida, para cada recurso, TripOptimizerImpl::ProcessLocation preenche o objeto Node correspondente usando a latitude e a longitude do local. Se mais de um recurso for retornado, o método TripOptimizerImpl::ProcessLocation adicionará o nó à variável 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);
}
Se a variável OptimizeTripParams::UnresolvedLocations não contiver elementos, o método TripOptimizerImpl::InternalOptimizeTripAsync passará para a segunda etapa, que é recuperar dados da rota do Bing Maps.
Para obter mais informações sobre o serviço de locais do Bing Maps, consulte API de locais.
Etapa 2: Recuperando dados de rota
Na segunda etapa, o método TripOptimizerImpl::InternalOptimizeTripAsync recupera dados de rota do Bing Maps. Os dados de rota são recuperados porque o algoritmo de otimização de viagem requer a distância entre cada conjunto de pontos no gráfico. Lembre-se que uma borda conecta dois nós no gráfico e cada nó contém um local.
//
// 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;
O método TripOptimizerImpl::RetrieveRoutesAsync segue um padrão semelhante ao do método TripOptimizerImpl::RetrieveLocationsAsync para recuperar dados de rota do Bing Maps.
// 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;
}
O método TripOptimizerImpl::ProcessRoute segue um padrão que se assemelha ao método TripOptimizerImpl::ProcessLocation para carregar dados XML. A diferença é que o método TripOptimizerImpl::ProcessRoute carrega informações de rota em um objeto Edge para um par de locais do trajeto.
//
// 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);
Depois que o método TripOptimizerImpl::RetrieveRoutesAsync processa todas as informações de rota, o método TripOptimizerImpl::InternalOptimizeTripAsync executa a etapa final, que é executar a otimização da rota.
Para obter mais informações sobre o serviço de rotas do Bing Maps, consulte API de rotas.
Etapa 3: Calculando a rota otimizada
Na terceira etapa, o método TripOptimizerImpl::InternalOptimizeTripAsync otimiza a rota para a menor distância geral percorrida. Ele chama a função AntSystem::OptimizeRoute, que usa nós e informações de borda, juntamente com outros parâmetros, como entradas para o algoritmo de otimização com colônia de formigas, para calcular a viagem otimizada.
// Run the simulation.
vector<size_t> routeIndices = OptimizeRoute(
params->Nodes, params->Edges,
params->Alpha, params->Beta, params->Rho,
params->Iterations,
cancellationToken,
&progressCallback,
params->Parallel);
Quando a função AntSystem::OptimizeRoute retorna, o método TripOptimizerImpl::InternalOptimizeTripAsync gira a ordem para corresponder à entrada do usuário. Em outras palavras, ele garante que a primeira entrada do usuário seja a primeira entrada na rota otimizada.
// 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());
Em seguida, o método TripOptimizerImpl::InternalOptimizeTripAsync cria os vetores paralelos que contêm dados de local (latitude e longitude) e nomes para exibição. Esses vetores estão contidos em um objeto Platform::Collections::Map. (Map é a implementação C++ da interface Windows::Foundation::Collections::IMap<K, V>. De forma similar, Platform::Collections::Vector é a implementação C++ da interface Windows::Foundation::Collections::IVector<T>.) O aplicativo principal usa os dados de local para exibir o mapa e nomes de locais como parte das instruções curva a curva.
//
// 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);
[Superior]
Definindo a funcionalidade HTTP
O componente C++ define a classe HttpRequest para processar solicitações HTTP. Essa classe usa a interface IXMLHTTPRequest2 para processar solicitações HTTP. A interface IXMLHTTPRequest2 oferece suporte somente a operações assíncronas. Para facilitar ao chamador consumir essas operações assíncronas, o método HttpRequest::GetAsync retorna um objeto concurrency::task<std::wstring>. Esse objeto de tarefa contém a resposta HTTP como uma cadeia de caracteres.
Dica
IXMLHTTPRequest2 é uma forma de se conectar ao serviço Web.Para conhecer outras opções de conexão a serviços em seu aplicativo, consulte Conectando serviços Web.
Como IXMLHTTPRequest2 oferece suporte apenas a operações assíncronas, você deve fornecer um objeto IXMLHTTPRequest2Callback ao solicitar dados de um servidor HTTP. O arquivo HttpRequest.cpp define a classe HttpRequestStringCallback, que herda dessa interface e implementa seus métodos.
Uma parte importante dessa implementação é o uso de concurrency::task_completion_event. Essa classe permite que a classe HttpReader crie uma tarefa que é definida quando outra tarefa assíncrona é concluída. Essa classe é útil quando você precisa compor objetos task juntamente com operações assíncronas que são concluídas por meio de retornos de chamada. Quando a operação de download é concluída com êxito, o método HttpRequestStringCallback::OnResponseReceived define o evento de conclusão.
completionEvent.set(make_tuple<HRESULT, wstring>(move(hr), move(wstr)));
Da mesma forma, o método HttpRequestStringCallback::OnError define o evento de conclusão quando ocorre um erro. Nesse caso, o código de erro e a cadeia de caracteres vazia são o resultado da tarefa.
completionEvent.set(make_tuple<HRESULT, wstring>(move(hrError), wstring()));
O método HttpRequest::GetAsync chama HttpRequest::DownloadAsync. O método HttpRequest::DownloadAsync abre a solicitação assíncrona e cria um objeto HttpRequestStringCallback. Em seguida, ele cria um objeto task que é concluído quando o evento de conclusão da tarefa do objeto HttpRequestStringCallback é concluído. Esse objeto usa task uma continuação para liberar o objeto HttpRequestStringCallback após o término do evento de conclusão da tarefa.
// 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);
});
}
Para obter informações sobre como a classe TripOptimizerImpl usa os dados XML resultantes para calcular a rota otimizada, consulte Fluxo de trabalho do componente neste documento. Para obter outros exemplos de como usar IXMLHTTPRequest2, consulte Quickstart: Connecting using XML HTTP Request e Instruções passo a passo: conexão usando tarefas e solicitações HTTP XML.
[Superior]
Calculando a rota ideal
O algoritmo principal que executa a computação da rota é definido em AntSystem.h e em AntSystem.cpp. A computação da rota lembra o problema do caixeiro viajante. O objetivo do problema do caixeiro viajante é receber uma coleção de locais e a distância entre cada par de locais e calcular a menor rota que visita cada local somente uma vez. O problema do caixeiro viajante é tradicionalmente difícil de resolver porque requer que cada rota possível seja calculada para encontrar a rota ideal. O algoritmo de otimização com colônia de formigas é uma abordagem meta-heurística para resolver esse tipo de problemas. Ele simula o comportamento de formigas para localizar rapidamente uma rota otimizada. Embora não haja garantia de que as rotas geradas por esse algoritmo sejam as menores para um determinado conjunto de locais, geralmente, ele localiza a menor rota ou a rota é suficientemente curta para fins de viagem.
Os arquivos AntSystem.h e AntSystem.cpp definem o namespace AntSystem. Esse namespace não contém dependências no Tempo de Execução do Windows e, portanto, não usa C++/CX. AntSystem.h define as estruturas LatLong, Node e Edge. Também define a função OptimizeRoute.
A estrutura LatLong representa a latitude e a longitude de um ponto em um mapa.
// 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;
};
A estrutura Node representa um nó em um gráfico. Ela contém o nome, a latitude e a longitude de um local. Também contém todos os nomes alternativos originários do serviço 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;
};
A estrutura Edge conecta dois nós e contém a distância do trajeto entre eles. Ela também contém os dados que são usados pelo algoritmo de otimização com colônia de formigas.
// 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;
};
O componente C++ cria um objeto Node para cada localidade na viagem e um objeto Edge para cada par de locais. Depois que coleta todas as informações necessárias dos serviços Web do Bing Maps, ele chama OptimizeRoute para calcular a rota ideal.
// 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);
Para resumir, esta documentação não descreve o algoritmo de otimização com colônia de formigas em detalhes. Para obter detalhes, consulte AntSystem.cpp no código-fonte.
No entanto, um aspecto importante da implementação do algoritmo é o uso de simultaneidade. O algoritmo de otimização com colônia de formigas executa várias iterações de três etapas básicas: permite que cada formiga percorra o gráfico, evapore o feromônio e, em seguida, permite que cada formiga trace seus passos de volta ao ponto inicial. A primeira etapa, permitir que cada formiga percorra o gráfico, pode ser executada paralelamente porque cada formiga age de forma independente. Essa etapa não contém dados compartilhados nem cálculos dependentes.
// 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();
});
}
Para obter mais informações sobre os algoritmos paralelos, como parallel_for_each, consulte Algoritmos paralelos.
Dica
O uso do sinalizador parallel não é uma parte necessária da implementação.Ele é fornecido como uma opção na interface do usuário para que você possa experimentar, com mais facilidade, a computação paralela em contraposição à serial.
[Superior]
Processando o cancelamento
As interfaces IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> e IAsyncOperationWithProgress<TResult, TProgress> fornecem um método Cancel que permite cancelar a operação assíncrona. Em C++, esses métodos Cancel cancelam um token de cancelamento associado com a operação assíncrona. Você pode conectar o cancelamento de tarefa com os métodos Tempo de Execução do Windows Cancel de duas maneiras. Primeiro, é possível definir a função de trabalho que você transmite a create_async para usar um objeto concurrency::cancellation_token. Quando o método Cancel é chamado, esse token de cancelamento é cancelado, e as regras de cancelamento normais são aplicadas. Se você não fornecer um objeto cancellation_token, o objeto task subjacente define um de forma implícita. Defina um objeto cancellation_token quando você precisa responder de forma cooperativa ao cancelamento na sua função de trabalho. Para obter mais informações sobre esse mecanismo de cancelamento, consulte Criando operações assíncronas n C++ para aplicativos da Windows Store.
O cancelamento ocorre quando o usuário escolhe o botão Cancel no aplicativo JavaScript ou quando ocorre um erro irrecuperável. Permitir que o usuário cancele é um motivo pelo qual é importante manter a interface do usuário responsiva durante a tarefa de otimização. O cancelamento não ocorre imediatamente. O componente C++ usa concurrency::cancellation_token_source e concurrency::cancellation_token para sinalizar e, ocasionalmente, verificar o cancelamento. O componente C++ implementa o cancelamento em um nível de mais alta granularidade possível, mas tenta permitir que o cancelamento ocorra em tempo hábil. Do ponto de vista de desempenho, o aplicativo não se beneficiaria se o cancelamento fosse verificado com muita frequência. Na verdade, o desempenho pode sofrer se o tempo gasto na verificação do cancelamento for maior do que o tempo gasto no trabalho.
O componente C++ verifica o cancelamento de duas maneiras diferentes. Primeiro, a tarefa de continuação que ocorre após cada estágio de otimização chama concurrency::task::get para testar o botão. O método task::get gera qualquer exceção que ocorreu durante a tarefa, incluindo task_canceled caso o cancelamento tenha ocorrido. (No caso de when_all, o tempo de execução escolhe uma das exceções se várias tarefas forem lançadas.) Como você deve observar todas as exceções de tarefa que ocorrem, definimos a função observe_all_exceptions para observar todas as exceções que ocorreram nas tarefas que foram fornecidas ao algoritmo when_all. O exemplo a seguir mostra a verificação de cancelamento depois que os locais são recuperados do Bing Maps, mas antes da recuperação das rotas.
//
// 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;
Após a chamada a observe_all_exceptions, relançamos a exceção original para que ela possa ser tratada pelo código que depende dessa tarefa.
O exemplo a seguir mostra observe_all_exceptions. Ele é iterado sobre cada objeto task na coleção fornecida e usa o método task::get para teste em busca de erros. Como planejamos relançar uma das exceções posteriormente, usamos blocos catch vazios para indicar que a exceção foi observada e tratada.
// 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.
}
});
});
}
A segunda maneira usada pelo componente para verificar o cancelamento é chamar o método concurrency::cancellation_token::is_canceled. O algoritmo de otimização de viagem (a função AntSystem::OptimizeRoute) verifica o cancelamento dessa forma a cada 100 milissegundos.
// 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;
}
Dica
Poderíamos ter usado a primeira técnica, que é chamar is_task_cancellation_requested e cancel_current_task, em vez de chamar cancellation_token::is_canceled.No entanto, cancel_current_task deve ser chamado de um objeto task.Como em teoria você pode usar essa implementação para chamar a função AntSystem::OptimizeRoute de uma task ou de outra parte do código, usamos tokens de cancelamento diretamente para serem flexíveis.Se essa função for chamada de um código que não usa tarefas, você poderá transmitir concurrency::cancellation_token::none ao parâmetro cancellationToken.O token none nunca pode ser cancelado.
A seção Definindo a funcionalidade HTTP descreve como a classe HttpRequestStringCallback usa task_completion_event para compor operações assíncronas que são concluídas por meio de retornos de chamada juntamente com objetos task. De forma similar, para oferecer suporte ao cancelamento, a classe HttpRequestStringCallback usa o método concurrency::cancellation_token::register_callback para registrar uma função de retorno de chamada que é chamada quando o token de cancelamento é cancelado. Essa técnica é útil porque a interface IXMLHTTPRequest2 executa o trabalho assíncrona que está fora de nosso controle. Quando o símbolo de cancelamento é cancelado, a função de retorno de chamada anula a solicitação HTTP e define o evento de conclusão de tarefa.
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 retorna um objeto concurrency::cancellation_token_registration que identifica o registro de retorno de chamada. O destruidor da classe HttpRequest usa esse objeto de registro para cancelar o registro da função de retorno de chamada. Recomendamos que você sempre cancele o registro de seu retorno de chamada quando não precisar mais garantir que todos os objetos sejam válidos quando uma função de retorno de chamada é chamada.
~HttpRequestStringCallback()
{
// Unregister the callback.
if (cancellationToken != cancellation_token::none())
{
cancellationToken.deregister_callback(registrationToken);
}
}
No caso de um erro irrecuperável, todas as tarefas restantes são canceladas. Por exemplo, se um documento XML não puder ser processado, a operação total será cancelada, e a exceção é lançada novamente.
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;
}
A classe TripOptimizerImpl define um objeto concurrency::cancellation_token_source porque o cancelamento é iniciado por meio dessa classe. Para permitir que o botão Cancel e o código interno cancelem as tarefas, a classe TripOptimizerImpl chama o método concurrency::cancellation_token_source::create_linked_source. Essa origem de token de cancelamento vinculada permite que o aplicativo JavaScript e a classe TripOptimizerImpl cancelem o mesmo token de cancelamento, mas de objetos cancellation_token_source diferentes.
// 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);
Para obter mais informações sobre como o cancelamento funciona na biblioteca Padrões Paralelos, consulte Cancelamento no PPL.
[Superior]
Migração do ActiveX
Para obter informações sobre como migramos da versão ActiveX do Bing Maps Trip Optimizer para um aplicativo Windows Store, consulte Migrando código existente no exemplo do Bing Maps Trip Optimizer.
[Superior]
Próximas etapas
Para obter informações sobre como o componente C++ Tempo de Execução do Windows interopera com o componente JavaScript, consulte Interoperando entre JavaScript e C++ no exemplo do Bing Maps Trip Optimizer.
[Superior]
Consulte também
Conceitos
Interoperando entre JavaScript e C++ no exemplo do Bing Maps Trip Optimizer
Usando JavaScript no exemplo do Bing Maps Trip Optimizer
Outros recursos
Desenvolvendo o Bing Maps Trip Optimizer, um aplicativo da Windows Store em JavaScript e C++