Compartir a través de


Usar C++ en el ejemplo de Bing Maps Trip Optimizer

En este documento se describe el componente de C++ de Optimizador de recorridos de Mapas de Bing. El componente de C++ usa código nativo puro para implementar la lógica de la optimización de rutas y código de Extensiones de componentes de Visual C++ (C++/CX) para comunicarse con otros componentes de Windows en tiempo de ejecución.

NotaNota

Para obtener más información sobre C++/CX, las extensiones de lenguaje que están disponibles para una aplicación de la Tienda Windows escrita en C++, consulta Referencia del lenguaje Visual C++ (C++/CX).

Para obtener información sobre cómo crear un componente de Windows en tiempo de ejecución en C++ básico, consulta Tutorial: Crear en C++ un componente básico de Windows en tiempo de ejecución y llamarlo desde JavaScript o C#.

La parte de C++ de la aplicación se escribe como una biblioteca de vínculos dinámicos (DLL) de Windows en tiempo de ejecución. Esta biblioteca proporciona la siguiente funcionalidad:

  • Recuperar la latitud y la longitud de cada ubicación del servicio web de Mapas de Bing. Esta operación utiliza la interfaz de Transferencia de estado de representación (REST) de Mapas de Bing.

  • Recuperar la distancia de desplazamiento entre cada par posible de puntos en el recorrido. Esta operación también utiliza la interfaz de REST de Mapas de Bing.

  • Realizar la optimización de la ruta. Esta operación utiliza el algoritmo de optimización basado en colonias de hormigas y el procesamiento en paralelo para calcular de forma eficaz la ruta optimizada.

Estas operaciones se realizan de forma asincrónica o en segundo plano. Esto permite que la interfaz de usuario siga respondiendo durante el proceso de optimización.

Estas son algunas de las características y tecnologías clave que el componente de C++ utiliza:

NotaNota

El código de ejemplo que corresponde a este documento se encuentra en el ejemplo del optimizador de recorridos de Mapas de Bing (Bing Maps Trip Optimizer).

En este documento

  • Convenciones de código

  • Organización de archivos

  • Crear las clases TripOptimizer y TripOptimizerImpl

  • Flujo de trabajo de componentes

    • Fase 1: recuperar datos de ubicación

    • Fase 2: recuperar datos de ruta

    • Fase 3: calcular la ruta optimizada

  • Definir la funcionalidad de HTTP

  • Calcular la ruta óptima

  • Controlar la cancelación

  • Migración de ActiveX

  • Pasos siguientes

Convenciones de código

Además de usar Windows en tiempo de ejecución para crear el componente de la Tienda Windows, el componente de C++ emplea convenciones de código modernas como punteros inteligentes y control de excepciones.

Windows en tiempo de ejecución es una interfaz de programación que puedes usar para crear aplicaciones de la Tienda Windows que solo se ejecutan en un entorno de sistema operativo de confianza. Esas aplicaciones usan funciones, tipos de datos y dispositivos autorizados, y se distribuyen desde la Tienda Windows. Windows en tiempo de ejecución se representado mediante la interfaz binaria de aplicación (ABI). La ABI es un contrato binario subyacente que pone las Windows en tiempo de ejecución API a disposición de los lenguajes de programación, como por ejemplo Visual C++.

Para facilitar la escritura de aplicaciones que utilizan Windows en tiempo de ejecución, Microsoft proporciona extensiones de lenguaje a Visual C++ que admiten específicamente Windows en tiempo de ejecución. Muchas de estas extensiones de lenguaje se asemejan a la sintaxis del lenguaje C++/CLI. Sin embargo, en lugar de tener como destino Common Language Runtime (CLR), las aplicaciones nativas usan esta sintaxis para establecer como destino Windows en tiempo de ejecución. El modificador "sombrero" (^) es una parte importante de esta nueva sintaxis porque permite la eliminación automática de objetos de runtime mediante el recuento de referencias. En lugar de llamar a métodos como AddRef y Release para administrar la vigencia de un objeto de Windows en tiempo de ejecución, el runtime elimina el objeto cuando ningún otro componente hace referencia a él, por ejemplo cuando sale del ámbito o cuando estableces todas las referencias en nullptr. Otra parte importante de Visual C++ para crear aplicaciones de la Tienda Windows es la palabra clave ref new. Usa ref new en lugar de new para crear objetos de Windows en tiempo de ejecución con recuento de referencias. Para obtener más información, consulta Objetos de Windows en tiempo de ejecución y referencia nuevos.

Nota importanteImportante

Solo tienes que usar ^ y ref new cuando creas objetos de Windows en tiempo de ejecución o componentes de Windows en tiempo de ejecución.Puedes utilizar la sintaxis de C++ estándar cuando escribas código básico de aplicaciones que no utilice Windows en tiempo de ejecución.

NotaNota

El Optimizador de recorridos de Mapas de Bing utiliza ^ junto con Microsoft::WRL::ComPtr, std::shared_ptr y std::unique_ptr para administrar objetos asignados por montón y minimizar las pérdidas de memoria.Recomendamos que utilices ^ para administrar la vigencia de las variables de Windows en tiempo de ejecución, Microsoft::WRL::ComPtr para administrar la vigencia de las variables de COM y shared_ptr o unique_ptr para administrar la vigencia de todos los demás objetos de C++ que se asignan en el montón.

Para obtener más información sobre la programación de C++ moderno, consulta Aquí está otra vez C++ (C++ moderno). Para obtener más información sobre las extensiones de lenguaje que están disponibles en una aplicación de la Tienda Windows de C++, consulta Referencia del lenguaje Visual C++ (C++/CX).

[Principio]

Organización de archivos

En la lista siguiente se describe brevemente el rol de cada archivo de código fuente que forma parte del componente de C++.

  • AntSystem.h, AntSystem.cpp
    Define el algoritmo de optimización basado en colonias de hormigas y sus estructuras de datos compatibles.

  • HttpRequest.h, HttpRequest.cpp
    Define la clase HttpRequest, que es una clase auxiliar para la realización de solicitudes HTTP asincrónicas.

  • pch.h, pch.cpp
    El encabezado precompilado del proyecto.

  • TripOptimizer.h, TripOptimizer.cpp
    Define la clase TripOptimizer, que actúa como interfaz entre la aplicación y la lógica básica del componente.

  • TripOptimizerImpl.h, TripOptimizerImpl.cpp
    Define la clase TripOptimizerImpl, que define la lógica básica del componente.

[Principio]

Crear las clases TripOptimizer y TripOptimizerImpl

El componente de C++ contiene una clase Windows en tiempo de ejecución, TripOptimizerComponent::TripOptimizer, que puede comunicarse con otros componentes de Windows en tiempo de ejecución. En Optimizador de recorridos de Mapas de Bing, esta clase se comunica con la parte de JavaScript de la aplicación. (Dado que el componente de C++ se escribe como un archivo DLL, también puedes utilizarlo con otras aplicaciones). La clase TripOptimizer define únicamente los métodos que se comunican con otros componentes de Windows en tiempo de ejecución. Los detalles de la implementación los controla la clase TripOptimizerImpl. Elegimos este patrón para encapsular mejor la interfaz pública y separarla de los detalles de implementación. Para obtener más información sobre este patrón, consulta Pimpl para encapsulación en tiempo de compilación (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;
};
Nota importanteImportante

Cuando crees una clase de componente de Windows en tiempo de ejecución que se puede compartir con otros componentes externos, asegúrate de utilizar las palabras clave public y sealed.Para obtener más información sobre cómo crear un componente de Windows en tiempo de ejecución en C++ que sea accesible desde una aplicación Tienda Windows, consulta Crear componentes de Windows en tiempo de ejecución en C++.

El método TripOptimizer::OptimizeTripAsync es el medio que tiene una aplicación para comunicarse con el componente de C++. Este método inicia la secuencia de tareas que calculan el recorrido optimizado. El llamador usa el valor devuelto para supervisar el progreso y la finalización de la tarea de optimización. También se utiliza para cancelar la operación. La cancelación se explica en la sección Controlar la cancelación más adelante en este documento.

El método TripOptimizer::OptimizeTripAsync se aplaza respecto a la clase TripOptimizerImpl para realizar la acción. TripOptimizerImpl se describe con más detalle posteriormente en este 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);
}

[Principio]

Flujo de trabajo de componentes

El método TripOptimizer::OptimizeTripAsync inicia la secuencia de operaciones que calculan el recorrido optimizado. Este método se comporta de forma asincrónica para permitir que la aplicación siga respondiendo. Para permitir el comportamiento asincrónico, este método devuelve Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>. Un componente de Windows en tiempo de ejecución que llame a este método podrá utilizar este objeto para obtener el valor devuelto cuando esté disponible. Esta interfaz también permite que el llamador supervise el progreso de la operación y reciba los errores que se produzcan.

SugerenciaSugerencia

Como procedimiento recomendado, el nombre de un método asincrónico debe terminar con Async y el método debe devolver Windows::Foundation::IAsyncAction, Windows::Foundation::IAsyncActionWithProgress<TProgress>, Windows::Foundation::IAsyncOperation<TResult> o Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>.

Cada lenguaje habilitado para Windows en tiempo de ejecución (C++, JavaScript, etc.) recomienda su propia forma de crear una operación asincrónica. En C++, puedes utilizar la función concurrency::create_async. Esta función devuelve un objeto IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> o IAsyncOperationWithProgress<TResult, TProgress>. El tipo de valor devuelto dependerá de la firma del objeto de función que le pases. Por ejemplo, como el método TripOptimizerImpl::InternalOptimizeTripAsync toma un objeto concurrency::progress_reporter como parámetro y devuelve un valor que no es void, create_async devuelve IAsyncOperationWithProgress<TResult, TProgress>. Si este método fuera a devolver void, create_async devolvería IAsyncActionWithProgress<TProgress>. Para obtener más información sobre create_async y cómo funciona con Windows 8, consulta Crear operaciones asincrónicas en C++ para aplicaciones de la Tienda Windows.

En el código siguiente se muestra el 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;
            }
        });
    });
}

El objeto progress_reporter comunica mensajes de progreso al llamador. El objeto cancellation_token permite al componente responder a las solicitudes de cancelación. (La cancelación se describe en la sección Controlar la cancelación de este documento). Para obtener más información sobre cómo la parte de JavaScript de la aplicación funciona con esta operación asincrónica que devuelve TripOptimizer::OptimizeTripAsync, consulta Interoperar entre JavaScript y C++ en el ejemplo de Bing Maps Trip Optimizer.

La función de trabajo que se proporciona a create_async en TripOptimizer::OptimizeTripAsync devuelve un objeto task. Puedes devolver un valor, T, o una tarea, task<T>, desde create_async. Devolvemos task<T> para que no tengamos que esperar el resultado de las tareas en segundo plano. En su lugar, dejamos que el runtime recupere el resultado cuando esté disponible y lo pase al llamador. Recomendamos que sigas este patrón cuando sea posible.

El archivo TripOptimizer.cpp define la función auxiliar task_from_result que devuelve un objeto task se completa inmediatamente con el resultado proporcionado. Esta función es útil cuando escribes una función que devuelve task y esa función realiza una devolución rápida con un resultado concreto.

// 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; });
}

En la ilustración siguiente se muestra el flujo de operaciones que se producen cuando un componente externo llama a TripOptimizer::OptimizeTripAsync para iniciar el proceso de optimización.

Flujo del componente C++

El método TripOptimizerImpl::InternalOptimizeTripAsync llama al método TripOptimizerImpl::OptimizeTripAsync como parte de la operación asincrónica. El método TripOptimizerImpl::CreateGraph llama a TripOptimizerImpl::InternalOptimizeTripAsync para crear un gráfico que represente el recorrido. Cada ubicación se representa mediante un nodo y cada par de nodos se conecta por un borde. Un nodo contiene información sobre una ubicación, como su nombre, su latitud y su longitud. Un borde contiene la distancia de desplazamiento entre un par de nodos.

// 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));
        });
    }
}

El método TripOptimizerImpl::InternalOptimizeTripAsync realiza la optimización de recorrido en tres fases. En la primera fase, este método recupera datos de ubicación de Mapas de Bing. En la segunda fase, este método recupera información de ruta entre cada par posible de puntos en el recorrido. En la tercera fase, este método realiza el algoritmo de optimización de recorrido. Cada fase actúa como una continuación de tarea de la fase anterior. Las continuaciones te permiten ejecutar una o varias tareas después de que otra tarea se complete. Las continuaciones se describen con más detalle posteriormente en este documento. Sin embargo, debes observar que, como una continuación se ejecuta en segundo plano, necesitas almacenar las variables que se comparten entre tareas para poder tener acceso a estas más adelante. La clase TripOptimizerImpl define la estructura OptimizeTripParams. Esta estructura contiene las entradas al método TripOptimizerImpl::InternalOptimizeTripAsync y las variables que se comparten entre las tareas que componen la operación global.

// 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;
};

El método TripOptimizerImpl::OptimizeTripAsync crea una estructura OptimizeTripParams (mediante un objeto std::shared_ptr) y la pasa a cada una de las continuaciones en la cadena de tareas.

// 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;
NotaNota

Podríamos haber creado estas variables como miembros directos de la clase TripOptimizerImpl.Sin embargo, mediante el suministro de una estructura de parámetro, asociamos de forma más clara el estado de una operación de optimización de recorrido con su acción.Si utilizáramos variables de miembro, sería más difícil admitir características tales como la ejecución simultánea de varias optimizaciones de recorrido.

Hh699891.collapse_all(es-es,VS.120).gifFase 1: recuperar datos de ubicación

En la primera fase, el método TripOptimizerImpl::InternalOptimizeTripAsync recupera datos de ubicación de Mapas de Bing. Los datos de ubicación se recuperan para que el usuario pueda confirmar que Mapas de Bing tiene la entrada correcta. Por ejemplo, si especificas “Pittsburgh”, Mapas de Bing no sabe si te refieres a "Pittsburgh, PA", "Pittsburgh, ON" o "Pittsburgh, GA". El componente de C++ tiene que recuperar posteriormente la distancia de desplazamiento entre cada par de ubicaciones. Por consiguiente, si una ubicación es ambigua, se deben mostrar todas las ubicaciones posibles en la interfaz de usuario para que el usuario puede seleccionar una o especificar una nueva. La variable OptimizeTripParams::UnresolvedLocations realiza un seguimiento de las ubicaciones no resueltas. Si el método TripOptimizerImpl::RetrieveLocationsAsync rellena este vector con valores, TripOptimizerImpl::InternalOptimizeTripAsync los devuelve.

//
// 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);
    }

El método TripOptimizerImpl::RetrieveLocationsAsync utiliza la clase concurrency::task para procesar las solicitudes HTTP como tareas en segundo plano. Dado que la clase HttpRequest actúa de forma asincrónica, TripOptimizerImpl::RetrieveLocationsAsync contiene todas las tareas en segundo plano y usa la función concurrency::when_all para definir el trabajo que se produce a continuación de que ellas finalicen. Como el método TripOptimizerImpl::RetrieveLocationsAsync forma parte de la tarea en segundo plano global, esta cadena de tareas de operaciones asincrónicas no bloquea la aplicación principal.

Los detalles sobre cómo funciona la clase HttpRequest se explican en la sección Definir la funcionalidad de HTTP de este 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(&params->RequestsPending);
        }));
    }

    return tasks;
}

El método TripOptimizerImpl::RetrieveLocationsAsync también usa el método concurrency::task::then para procesar cada respuesta de Mapas de Bing cuando llega. El método task::then crea una tarea de continuación, que es una tarea que se ejecuta después de que la tarea anterior, o precedente, finalice. La llamada a when_all al final del método TripOptimizerImpl::RetrieveLocationsAsync espera a que todas las tareas y sus tareas de continuación finalicen. Para obtener más información sobre tareas y continuaciones en C++, consulta Paralelismo de tareas.

La API de REST de Mapas de Bing devuelve datos XML. El método TripOptimizerImpl::ProcessLocation carga la información de ubicación correspondiente a una ubicación desde la secuencia de datos XML proporcionada. Este método utiliza XmlDocument::SelectSingleNodeNS para procesar el objeto XmlDocument proporcionado. En este ejemplo se muestra cómo el método TripOptimizerImpl::ProcessLocation recupera el código de respuesta de la solicitud:

// 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");
Nota importanteImportante

Asegúrate de utilizar la notación text() cuando uses XmlDocument::SelectSingleNodeNS para seleccionar nodos de texto de un documento XML.

El método TripOptimizerImpl::ProcessLocation produce una excepción si se produce un error. En este ejemplo, TripOptimizerImpl::ProcessLocation produce la excepción Platform::NullReferenceException si el documento XML no contiene los datos esperados. Como este error no es recuperable, el componente no detecta esta excepción. Por consiguiente, si se produce una excepción, se pasa al controlador de errores en la aplicación principal.

El método TripOptimizerImpl::ProcessLocation lee el número de recursos totales de la secuencia XML. Por recurso se entiende una coincidencia posible para un nombre de ubicación. Por ejemplo, si especificas “Pittsburgh”, Mapas de Bing podría devolver “Pittsburgh, PA”, "Pittsburgh, ON” y “Pittsburgh, GA” como posibilidades. Para cada recurso, TripOptimizerImpl::ProcessLocation rellena el objeto Node correspondiente mediante la latitud y la longitud de la ubicación. Si se devuelven varios recursos, el método TripOptimizerImpl::ProcessLocation agrega el nodo a la variable 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);
}

Si la variable OptimizeTripParams::UnresolvedLocations no contiene elementos, el método TripOptimizerImpl::InternalOptimizeTripAsync pasa a la segunda fase, que consiste en recuperar datos de ruta de Mapas de Bing.

Para obtener más información sobre el servicio de ubicaciones de Mapas de Bing, consulta API de ubicaciones.

Hh699891.collapse_all(es-es,VS.120).gifFase 2: recuperar datos de ruta

En la segunda fase, el método TripOptimizerImpl::InternalOptimizeTripAsync recupera datos de ruta de Mapas de Bing. Los datos de ruta se recuperan porque el algoritmo de optimización de recorrido necesita la distancia entre cada conjunto de puntos en el gráfico. Recuerda que un borde conecta dos nodos en el gráfico y cada nodo contiene una ubicación.

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

El método TripOptimizerImpl::RetrieveRoutesAsync sigue un patrón similar al del método TripOptimizerImpl::RetrieveLocationsAsync para recuperar datos de ruta de Mapas de Bing.

// 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(&params->RequestsPending);
        }));
    }

    return tasks;
}

El método TripOptimizerImpl::ProcessRoute sigue un patrón similar al del método TripOptimizerImpl::ProcessLocation para cargar datos XML. La diferencia es que el método TripOptimizerImpl::ProcessRoute carga la información de ruta en un objeto Edge para un par de ubicaciones de recorrido.

//
// 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);

Después de que el método TripOptimizerImpl::RetrieveRoutesAsync procese toda la información de ruta, el método TripOptimizerImpl::InternalOptimizeTripAsync realiza la fase final, que consiste en realizar la optimización de ruta.

Para obtener más información sobre el servicio de rutas de Mapas de Bing, consulta API de rutas.

Hh699891.collapse_all(es-es,VS.120).gifFase 3: calcular la ruta optimizada

En la tercera fase, el método TripOptimizerImpl::InternalOptimizeTripAsync optimiza la ruta para la distancia total más corta recorrida. Llama a la función AntSystem::OptimizeRoute, que utiliza información de nodos y de borde, junto con otros parámetros, como entradas para el algoritmo de optimización basado en colonias de hormigas, para calcular el recorrido optimizado.

// Run the simulation.
vector<size_t> routeIndices = OptimizeRoute(
    params->Nodes, params->Edges, 
    params->Alpha, params->Beta, params->Rho, 
    params->Iterations, 
    cancellationToken,
    &progressCallback, 
    params->Parallel);

Cuando la función AntSystem::OptimizeRoute devuelve resultados, el método TripOptimizerImpl::InternalOptimizeTripAsync invierte el orden para lograr la coincidencia con la entrada de usuario. Es decir, garantiza que la primera entrada del usuario sea la primera entrada de la ruta optimizada.

// 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());

El método TripOptimizerImpl::InternalOptimizeTripAsync crea después los vectores paralelos que contienen los datos de ubicación (latitud y longitud) y los nombres para mostrar. Estos vectores están contenidos en un objeto Platform::Collections::Map. (Map es la implementación de C++ para la interfaz Windows::Foundation::Collections::IMap<K, V>. De igual forma, Platform::Collections::Vector es la implementación de C++ para la interfaz Windows::Foundation::Collections::IVector<T>). La aplicación principal utiliza los datos de ubicación para mostrar el mapa y los nombres de ubicación como parte de las instrucciones actualizadas a cada cambio de dirección.

//
// 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);

[Principio]

Definir la funcionalidad de HTTP

El componente de C++ define la clase HttpRequest para procesar las solicitudes HTTP. Esta clase utiliza la interfaz IXMLHTTPRequest2 para procesar las solicitudes HTTP. La interfaz IXMLHTTPRequest2 solo admite operaciones asincrónicas. Para facilitar al llamador la utilización de estas operaciones asincrónicas, el método HttpRequest::GetAsync devuelve un objeto concurrency::task<std::wstring>. Este objeto de tarea contiene la respuesta HTTP en forma de cadena.

NotaNota

IXMLHTTPRequest2 es un modo de conectarse al servicio Web.Para obtener información sobre otras opciones para conectarse a los servicios de la aplicación, consulta Conexión a servicios web.

Dado que IXMLHTTPRequest2 solo admite operaciones asincrónicas, debes proporcionar un objeto IXMLHTTPRequest2Callback cuando solicites datos de un servidor HTTP. El archivo HttpRequest.cpp define la clase HttpRequestStringCallback, que hereda de esta interfaz e implementa sus métodos.

Una parte importante de esta implementación es el uso de concurrency::task_completion_event. Esta clase permite a la clase HttpReader crear una tarea que se establecerá cuando otra tarea asincrónica se complete. Esta clase es útil si necesitas crear objetos task junto con operaciones asincrónicas que se completen mediante devoluciones de llamada. Cuando la operación de descarga se completa correctamente, el método HttpRequestStringCallback::OnResponseReceived establece el evento de finalización.

completionEvent.set(make_tuple<HRESULT, wstring>(move(hr), move(wstr)));

En consecuencia, el método de HttpRequestStringCallback::OnError establece el evento de finalización cuando se produce un error. En este caso, el código de error y la cadena vacía son el resultado de la tarea.

completionEvent.set(make_tuple<HRESULT, wstring>(move(hrError), wstring()));

El método HttpRequest::GetAsync llama a HttpRequest::DownloadAsync. El método HttpRequest::DownloadAsync abre la solicitud asincrónica y crea un objeto HttpRequestStringCallback. A continuación, crea un objeto task que se completa cuando se completa el evento de finalización de tarea del objeto HttpRequestStringCallback. Este objeto task utiliza una continuación para liberar el objeto HttpRequestStringCallback después de que el evento de finalización de tarea se complete.

// 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 obtener información sobre cómo la clase TripOptimizerImpl utiliza los datos XML resultantes para calcular la ruta optimizada, consulta la sección Flujo de trabajo de componentes de este documento. Para obtener ejemplos de cómo usar IXMLHTTPRequest2, consulta Quickstart: Connecting using XML HTTP Request y Tutorial: Conectar usando tareas y solicitudes HTTP XML.

[Principio]

Calcular la ruta óptima

El algoritmo básico que realiza el cálculo de la ruta se define en AntSystem.h y AntSystem.cpp. El cálculo de la ruta guarda similitud con el problema del viajante de comercio. El objetivo del problema del viajante de comercio es tomar una serie de ubicaciones y la distancia entre cada par de ubicaciones y calcular la ruta más corta que visita cada ubicación solamente una vez. Este problema es tradicionalmente difícil de resolver porque requiere el cálculo de cada ruta posible para encontrar la ruta óptima. El algoritmo de optimización basado en colonias de hormigas es un enfoque metaheurístico para resolver este tipo de problemas. Modela el comportamiento de las hormigas para encontrar rápidamente una ruta optimizada. Aunque no se garantiza que las rutas generadas por este algoritmo sean las más cortas para un conjunto determinado de ubicaciones, suele buscar la ruta más corta o una ruta suficientemente corta a efectos de desplazamiento.

Los archivos AntSystem.h y AntSystem.cpp definen el espacio de nombres AntSystem. Este espacio de nombres no contiene dependencias en Windows en tiempo de ejecución y, por consiguiente, no utiliza C++/CX. El archivo AntSystem.h define las estructuras LatLong, Node y Edge. También define la función OptimizeRoute.

La estructura LatLong representa la latitud y la longitud de un punto en un 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;
};

La estructura Node representa un nodo en un gráfico. Contiene el nombre, la latitud y la longitud de una ubicación. También contiene los nombres alternativos que proceden del servicio de Mapas de Bing.

// 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;
};

La estructura Edge conecta dos nodos y contiene la distancia de desplazamiento entre ellos. También contiene los datos utilizados por el algoritmo de optimización basado en colonias de hormigas.

// 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;
};

El componente de C++ crea un objeto Node para cada ubicación en el recorrido y un objeto Edge para cada par de ubicaciones. Después de recopilar toda la información necesaria de los servicios web de Mapas de Bing, llama a OptimizeRoute para calcular la ruta óptima.

// 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);

Por razones de brevedad, esta documentación no describe de forma detallada el algoritmo de optimización de colonias de hormigas. Para obtener información detallada, consulta AntSystem.cpp en el código fuente.

Sin embargo, un aspecto importante de la implementación del algoritmo es el uso de la simultaneidad. El algoritmo de optimización de colonias de hormigas realiza varias iteraciones de tres pasos básicos: permitir que cada hormiga recorra el gráfico, evaporar feromona y permitir después que cada hormiga vuelva sobre sus pasos al punto inicial. El primer paso que permite que cada hormiga recorra el gráfico, puede realizarse en paralelo porque cada una actúa independientemente. Este paso no contiene datos compartidos ni cálculos dependientes.

// 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 obtener más información acerca de los algoritmos en paralelo como parallel_for_each, consulta Algoritmos paralelos.

NotaNota

El uso de la marca parallel no es una parte necesaria de la implementación.Se proporciona como una opción en la interfaz de usuario para que puedas experimentar más fácilmente con el cálculo en paralelo frente al cálculo en serie.

[Principio]

Controlar la cancelación

Las interfaces IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> y IAsyncOperationWithProgress<TResult, TProgress> proporcionan cada una un método Cancel que te permite cancelar la operación asincrónica. En C++, estos métodos Cancel cancelan un token de cancelación que está asociado a la operación asincrónica. Puedes conectar la cancelación de tareas con los métodos Windows en tiempo de ejecución Cancel de dos maneras. Primero, puedes definir la función de trabajo que pasas a create_async para que tome un objeto concurrency::cancellation_token. Cuando se llama al método Cancel, este token de cancelación se cancela y se aplican las reglas normales de cancelación. Si no proporcionas un objeto cancellation_token, el objeto subyacente task define uno implícitamente. Después, tienes que definir un objeto cancellation_token cuando necesites responder de forma cooperativa a la cancelación en tu función de trabajo. Para obtener más información sobre este mecanismo de cancelación, consulta Crear operaciones asincrónicas en C++ para aplicaciones de la Tienda Windows.

La cancelación se produce cuando el usuario elige el botón Cancel en la aplicación JavaScript o se produce un error irrecuperable. Permitir que el usuario pueda cancelar es una de las razones por las que es importante que la interfaz de usuario siga respondiendo durante la tarea de optimización. La cancelación no se produce de forma inmediata. El componente de C++ usa concurrency::cancellation_token_source y concurrency::cancellation_token para indicar la cancelación y comprobar ocasionalmente la cancelación. El componente de C++ implementa la cancelación en el nivel más amplio y general posible, pero sigue intentando que la cancelación se produzca de manera oportuna. Desde el punto de vista del rendimiento, la aplicación no saldría beneficiada si comprobase la cancelación con demasiada frecuencia. De hecho, el rendimiento puede verse afectado si el tiempo empleado en comprobar la cancelación es superior al tiempo empleado en realizar el trabajo.

El componente de C++ comprueba la cancelación de dos maneras diferentes. Primero, la tarea de continuación que se produce después de cada fase de optimización llame a concurrency::task::get para comprobar la cancelación. El método task::get produce cualquier excepción que tenga lugar durante la tarea, incluida task_canceled si se produce la cancelación. (En el caso de when_all, el runtime elige una de las excepciones si se producen varias en las tareas). Como debes observar todas las excepciones de tarea que se produzcan, definimos la función observe_all_exceptions para observar todas las excepciones que se produzcan en las tareas que se proporcionan al algoritmo when_all. En el ejemplo siguiente se muestra la comprobación de la cancelación después de que se recuperen las ubicaciones de Mapas de Bing pero antes de que se recuperen las rutas.

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

Después de la llamada a observe_all_exceptions, volvemos a producir la excepción original para que pueda ser controlada por el código que depende de dicha tarea.

En el siguiente código se muestra observe_all_exceptions. Recorre en iteración cada objeto task de la colección proporcionada y utiliza el método task::get para comprobar los errores. Como tenemos previsto volver a producir más adelante una de las excepciones, utilizamos bloques catch vacíos para indicar que la excepción se ha observado y controlado.

// 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.
            }
        });
    });
}

La segunda manera que el componente utiliza para comprobar la cancelación consiste en llamar al método concurrency::cancellation_token::is_canceled. El algoritmo de optimización de recorrido (la función AntSystem::OptimizeRoute) comprueba la cancelación de esta manera cada 100 milisegundos.

// 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;
    }
NotaNota

Podríamos haber utilizado la primera técnica, que consiste en llamar a is_task_cancellation_requested y cancel_current_task, en lugar de llamar a cancellation_token::is_canceled.Sin embargo, cancel_current_task se debe invocar desde un objeto task.Como en teoría puedes utilizar esta implementación para llamar a la función AntSystem::OptimizeRoute desde un objeto task o desde otra parte del código, utilizamos tokens de cancelación directamente para ser flexibles.Si se llamase a esta función desde código que no usa tareas, podrías pasar concurrency::cancellation_token::none al parámetro cancellationToken.El token none nunca puede cancelarse.

La sección Definir la funcionalidad de HTTP describe la forma en que la clase HttpRequestStringCallback utiliza task_completion_event para la creación de operaciones asincrónicas que se completan mediante devoluciones de llamada junto con objetos task. De igual forma, para admitir la cancelación, la clase HttpRequestStringCallback utiliza el método concurrency::cancellation_token::register_callback para registrar una función de devolución de llamada que se invoca cuando se cancela el token de cancelación. Esta técnica es útil porque la interfaz IXMLHTTPRequest2 realiza el trabajo asincrónico que está fuera de nuestro control. Cuando se cancela el token de cancelación, la función de devolución de llamada anula la solicitud HTTP y establece el evento de finalización de tarea.

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 devuelve un objeto concurrency::cancellation_token_registration que identifica el registro de devolución de llamada. El destructor de la clase HttpRequest utiliza este objeto de registro para anular el registro de la función de devolución de llamada. Te recomendamos que anules siempre el registro de tu devolución cuando ya no lo necesites para asegurarte de que todos los objetos sean válidos cuando se invoque una función de devolución de llamada.

~HttpRequestStringCallback()
{
    // Unregister the callback.
    if (cancellationToken != cancellation_token::none())
    {
        cancellationToken.deregister_callback(registrationToken);
    }
}

En el caso de un error irrecuperable, las tareas restantes se cancelan. Por ejemplo, si un documento XML no se puede procesar, la operación global se cancela y se vuelve a producir la excepción.

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;
}

La clase TripOptimizerImpl define un objeto concurrency::cancellation_token_source porque la cancelación se inicia a través de esta clase. Para permitir que el botón Cancel y el código interno cancelen las tareas, la clase TripOptimizerImpl llama al método concurrency::cancellation_token_source::create_linked_source. Este origen de token de cancelación vinculado permite a la aplicación JavaScript y a la clase TripOptimizerImpl cancelar el mismo token de cancelación, pero desde 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 obtener más información sobre cómo funciona la cancelación en la biblioteca de patrones de procesamiento en paralelo, consulta Cancelación en la biblioteca PPL.

[Principio]

Migración de ActiveX

Para obtener información sobre cómo migramos de la versión ActiveX de Optimizador de recorridos de Mapas de Bing a una aplicación de la Tienda Windows, consulta Migrar código existente en el ejemplo de Bing Maps Trip Optimizer.

[Principio]

Pasos siguientes

Para obtener información sobre cómo interactúa el componente de Windows en tiempo de ejecución en C++ con el componente de JavaScript, consulta Interoperar entre JavaScript y C++ en el ejemplo de Bing Maps Trip Optimizer.

[Principio]

Vea también

Conceptos

Interoperar entre JavaScript y C++ en el ejemplo de Bing Maps Trip Optimizer

Usar JavaScript en el ejemplo de Bing Maps Trip Optimizer

Otros recursos

Desarrollar el optimizador de recorridos de Mapas de Bing, una aplicación de la Tienda Windows en JavaScript y C++