Utilisation de C++ dans l'exemple de l'optimiseur de voyage Bing Maps

Ce document décrit le composant C++ de l'application Bing Maps Trip Optimizer. Le composant C++ utilise le code natif pure pour implémenter la logique d'optimisation de routage et du code Extensions de composant Visual C++ (C++/CX) pour interagir avec d'autres composants Windows Runtime.

Notes

Pour plus d'informations sur C++/CX, les extensions de langage disponibles pour les applications Windows Store écrites en C++, consultez Référence du langage Visual C++ (C++/CX).

Pour plus d'informations sur la création d'un composant Windows Runtime C++ de base, consultez Procédure pas à pas : création d'un composant Windows Runtime de base en C++ et appel de ce composant depuis JavaScript.

La partie C++ de l'application est écrite sous la forme d'une bibliothèque de liens dynamiques (Dynamic-Link Library, DLL) Windows Runtime. Cette bibliothèque fournit cette fonctionnalité :

  • Récupérer la latitude et la longitude de chaque emplacement à partir du service Web Bing Maps. Cette opération utilise l'interface Bing Maps REST (Representational State Transfer).

  • Récupérer la distance entre chaque paire de points possible dans un voyage. Cette opération utilise également l'interface Bing Maps REST.

  • Exécuter l'optimisation de l'itinéraire. Cette opération utilise l'algorithme d'optimisation de colonies de fourmis et le traitement parallèle pour calculer efficacement l'itinéraire optimisé.

Ces opérations sont exécutées de façon asynchrone, ou en arrière-plan. Cela permet à l'interface utilisateur de rester réactif pendant le processus d'optimisation.

Voici quelques-unes des principales fonctionnalités et technologies utilisées par le composant C++ :

Notes

L'exemple de code qui correspond à ce document se trouve dans l'Exemple Optimiseur de voyage Bing Maps.

Dans ce document :

  • Conventions de code

  • Organisation des fichiers

  • Création des classes TripOptimizer et TripOptimizerImpl

  • Flux de travail du composant

    • Phase 1 : Récupération des données d'emplacement

    • Phase 2 : Récupération des données d'itinéraire

    • Phase 3 : Calcul de l'itinéraire optimisé

  • Définition des fonctionnalités HTTP

  • Calcul de l'itinéraire optimal

  • Gestion de l'annulation

  • Migration à partir d'ActiveX

  • Étapes suivantes

Conventions de code

En plus d'utiliser Windows Runtime pour créer le composant Windows Store, le composant C++ utilise des conventions de code modernes telles que les pointeurs intelligents et la gestion des exceptions.

Windows Runtime est une interface de programmation permettant de créer des applications Windows Store qui ne peuvent être exécutées que dans un système d'exploitation fiable. Ces applications utilisent des fonctionnalités, des types de données et des appareils autorisés, et sont distribuées via le Windows Store. Windows Runtime est représenté par l'interface binaire d'application (ABI). ABI est un contrat binaire sous-jacent qui rend les API Windows Runtime disponibles pour les langages de programmation tels que Visual C++.

Pour simplifier l'écriture d'applications qui utilisent Windows Runtime, Microsoft fournit des extensions de langage à Visual C++ conçues spécialement pour prendre en charge Windows Runtime. Plusieurs de ces extensions de langage possèdent une syntaxe similaire à celle du langage C++/CLI. Toutefois, au lieu de cibler le Common Langage Runtime (CLR), les applications natives utilisent cette syntaxe pour cibler le Windows Runtime. Le modificateur (^) accent circonflexe est un élément important de cette nouvelle syntaxe, car il permet l'effacement automatique des objets d'exécution au moyen du décompte de références. Au lieu d'appeler des méthodes telles que AddRef et Release pour gérer la durée de vie d'un objet Windows Runtime, le runtime supprime l'objet lorsqu'aucun autre composant ne le référence, comme par exemple, lorsqu'il quitte la portée ou lorsque vous définissez toutes les références sur nullptr. Un autre élément important de Visual C++ pour la création d'applications Windows Store est le mot clé ref new. Utilisez le mot clé ref new au lieu de new pour créer des objets Windows Runtime dont les références font l'objet d'un décompte. Pour plus d'informations sur les rappels XAudio2, consultez les ref new et objets Windows Runtime.

Important

Il vous suffit d'utiliser ^ et ref new lorsque vous créez des objets Windows Runtime ou des composants Windows Runtime. Vous pouvez utiliser la syntaxe C++ standard lorsque vous écrivez le code d'une application de base n'utilisant pas le Windows Runtime.

Notes

L'optimiseur de voyage Bing Maps utilise ^ conjointement avec Microsoft::WRL::ComPtr, std::shared_ptr et std::unique_ptr pour gérer les objets alloués par tas et réduire les fuites de mémoire. Nous vous recommandons d'utiliser ^ pour gérer la durée de vie des variables Windows Runtime, Microsoft::WRL::ComPtr pour gérer la durée de vie des variables COM, et shared_ptr ou unique_ptr pour gérer la durée de vie de tous les autres objets C++ qui sont alloués sur le tas.

Pour plus d'informations sur la programmation C++ moderne, consultez Accueil vers C++ (C++ moderne). Pour plus d'informations sur les extensions de langage disponibles pour les applications C++ Windows Store, consultez Référence du langage Visual C++ (C++/CX).

[Haut]

Organisation des fichiers

La liste suivante décrit brièvement le rôle de chaque fichier de code source qui fait partie du composant C++.

  • AntSystem.h, AntSystem.cpp
    Définit l'algorithme d'optimisation de colonies de fourmis et les structures de données de prise en charge.

  • HttpRequest.h, HttpRequest.cpp
    Définit la classe HttpRequest, qui est une classe d'assistance pour effectuer des requêtes HTTP asynchrones.

  • pch.h, pch.cpp
    Définit l'en-tête précompilé du projet.

  • TripOptimizer.h, TripOptimizer.cpp
    Définit la classe TripOptimizer, qui sert d'interface entre l'application et la logique de composant principale.

  • TripOptimizerImpl.h, TripOptimizerImpl.cpp
    Définit la classe TripOptimizerImpl qui définit la logique principale du composant.

[Haut]

Création des classes TripOptimizer et TripOptimizerImpl

Le composant C++ contient une classe Windows Runtime, TripOptimizerComponent::TripOptimizer, pouvant faire interface avec d'autres composants Windows Runtime. Dans Bing Maps Trip Optimizer, cette classe interagit avec la partie JavaScript de l'application. (Étant donné que le composant C++ est écrit en tant que DLL, vous pouvez également l'utiliser avec d'autres applications.) La classe TripOptimizer définit uniquement les méthodes qui communiquent avec d'autres composants Windows Runtime. Les détails d'implémentation sont gérés par la classe TripOptimizerImpl. Nous avons choisi ce modèle pour mieux encapsuler l'interface publique et la séparer des détails d'implémentation. Pour plus d'informations sur ce modèle, consultez Pimpl pour l'encapsulation de compilation C++ (moderne).

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

Important

Lorsque vous créez une classe de composant Windows Runtime qui peut être partagée avec d'autres composants externes, assurez-vous d'utiliser les mots clés public et sealed. Pour plus d'informations sur le mode de création d'un composant Windows Runtime en C++ pouvant être appelé depuis une application Windows Store, consultez la section Création de composants Windows Runtime en C++.

La méthode TripOptimizer::OptimizeTripAsync définit la manière dont une application communique avec le composant C++. Cette méthode démarre la séquence de tâches qui calcule le voyage optimisé. L'appelant utilise la valeur de retour pour surveiller la progression et l'exécution de la tâche d'optimisation. Elle est également utilisée pour annuler l'opération. L'annulation est expliquée dans la section Gestion de l'annulation plus loin dans ce document.

La méthode TripOptimizer::OptimizeTripAsync diffère de la classe TripOptimizerImpl pour exécuter l'action. TripOptimizerImpl est décrit plus en détail ultérieurement dans ce document.

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

[Haut]

Flux de travail du composant

La méthode TripOptimizer::OptimizeTripAsync commence la séquence d'opérations qui calculent le voyage optimisé. Cette méthode se comporte de manière asynchrone pour permettre à l'application de rester réactive. Pour activer le comportement asynchrone, cette méthode retourne Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>. Un composant Windows Runtime qui appelle cette méthode peut utiliser cet objet pour obtenir la valeur de retour lorsqu'il est disponible. Cette interface permet également à l'appelant de contrôler le déroulement de l'exécution et de recevoir toutes les erreurs qui se produisent.

Chaque langage activé Windows Runtime (C++, JavaScript, etc.) fournit sa propre méthode pour créer une opération asynchrone. En C++, vous pouvez utiliser la fonction concurrency::create_async. Cette fonction retourne un objet IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> ou IAsyncOperationWithProgress<TResult, TProgress>. Le type de retour dépend de la signature de l'objet de fonction que vous passez à celui-ci. Par exemple, la méthode TripOptimizerImpl::InternalOptimizeTripAsync prend un objet concurrency::progress_reporter comme paramètre et retourne une valeur non void, create_async retourne IAsyncOperationWithProgress<TResult, TProgress>. Si cette méthode consistait à retourner void, create_async retournerait IAsyncActionWithProgress<TProgress>. Pour plus d'informations sur create_async et sur son fonctionnement avec Windows 8, consultez Création d'opérations asynchrones en C++ pour les applications Windows Store.

Le code suivant montre la méthode 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;
            }
        });
    });
}

L'objet progress_reporter communique les messages de progression à l'appelant. L'objet cancellation_token permet au composant de répondre aux demandes d'annulation. (L'annulation est décrite dans la section Gestion de l'annulation de ce document.) Pour plus d'informations sur la façon dont la partie JavaScript de l'application fonctionne avec cette opération asynchrone qui retourne TripOptimizer::OptimizeTripAsync, consultez Interopérabilité entre JavaScript et C++ dans l'exemple de l'optimiseur de voyage Bing Maps.

La fonction de travail fournie à create_async dans TripOptimizer::OptimizeTripAsync retourne un objet task. Vous pouvez retourner une valeur, T, ou une tâche, task<T>, à partir de create_async. Nous renvoyons task<T> afin de ne pas avoir à attendre le résultat des tâches d'arrière-plan. Au contraire, nous laissons le runtime récupérer le résultat lorsqu'il est disponible et le passer à l'appelant. Nous vous recommandons de suivre ce modèle si possible.

Le fichier TripOptimizer.cpp définit la fonction d'assistance task_from_result qui retourne un objet task qui se termine immédiatement avec le résultat fourni. Cette fonction est utile lorsque vous écrivez une fonction qui retourne task et que cette fonction retourne un résultat spécifique de façon anticipée.

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

L'illustration suivante montre le flux des opérations qui se produisent lorsqu'un composant externe appelle TripOptimizer::OptimizeTripAsync pour démarrer le processus d'optimisation.

Flux de composant C++

La méthode TripOptimizerImpl::InternalOptimizeTripAsync appelle la méthode TripOptimizerImpl::OptimizeTripAsync dans le cadre de l'opération asynchrone. La méthode TripOptimizerImpl::CreateGraph appelle TripOptimizerImpl::InternalOptimizeTripAsync pour créer un graphique qui représente un voyage. Chaque emplacement est représenté par un nœud, et chaque paire de nœuds est connectée à un bord. Un nœud contient des informations sur un emplacement (son nom, sa latitude et sa longitude, par exemple). Un bord contient la distance entre une paire de nœuds.

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

La méthode TripOptimizerImpl::InternalOptimizeTripAsync exécute l'optimisation du voyage en trois phases. Dans la première phase, cette méthode récupère des données d'emplacement à partir de Bing Maps. Dans la deuxième phase, elle extrait les informations d'itinéraire entre chaque paire de points possible dans un voyage. Dans la troisième phase, cette méthode exécute l'algorithme d'optimisation de voyage. Chaque phase fait office de continuation de tâche de la phase précédente. Les continuations vous autorisent à exécuter une ou plusieurs tâches une fois qu'une tâche est terminée. Les continuations sont décrites plus en détails ultérieurement dans ce document. Toutefois, comme une continuation s'exécute en arrière-plan, vous devez enregistrer les variables qui sont partagées entre des tâches afin qu'elles puissent y accéder ultérieurement. La classe TripOptimizerImpl définit la structure OptimizeTripParams. Cette structure contient les entrées de la méthode TripOptimizerImpl::InternalOptimizeTripAsync et les variables qui sont partagées entre les tâches qui impliquent l'opération globale.

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

La méthode TripOptimizerImpl::OptimizeTripAsync crée une structure OptimizeTripParams (à l'aide d'un objet std::shared_ptr) et le passe à chacune des continuations dans la chaîne de tâche.

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

Notes

Ces variables auraient pu être créées comme membres directs de la classe TripOptimizerImpl. Toutefois, en fournissant une structure de paramètre, nous associons plus clairement l'état d'une opération d'optimisation de voyage avec son action. Si nous utilisons des variables membres, il est plus difficile de prendre en charge des fonctionnalités telles que l'exécution simultanée de plusieurs optimisations de voyage.

Hh699891.collapse_all(fr-fr,VS.110).gifPhase 1 : Récupération des données d'emplacement

Dans la première phase, la méthode TripOptimizerImpl::InternalOptimizeTripAsync récupère des données d'emplacement à partir de Bing Maps. Les données d'emplacement sont récupérées afin que l'utilisateur puisse vérifier que Bing Maps a l'entrée appropriée. Par exemple, si vous spécifiez « Pittsburgh », Bing Maps ne sait pas si vous voulez dire « Pittsburgh, PA », « Pittsburgh, ON », ou « Pittsburgh, GA ». Le composant C++ doit ensuite extraire la distance entre chaque paire d'emplacements. Par conséquent, si un emplacement est ambigu, tous les emplacements possibles doivent être affichés dans l'interface utilisateur afin que l'utilisateur puisse en sélectionner un ou en écrire un. La variable OptimizeTripParams::UnresolvedLocations suit les emplacements non résolus. Si la méthode TripOptimizerImpl::RetrieveLocationsAsync remplit le vecteur avec n'importe quelle valeur, TripOptimizerImpl::InternalOptimizeTripAsync les retourne.

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

La méthode TripOptimizerImpl::RetrieveLocationsAsync utilise la classe concurrency::task pour traiter des requêtes HTTP en tant que tâches en arrière-plan. Étant donné que la classe HttpRequest agit de façon asynchrone, TripOptimizerImpl::RetrieveLocationsAsync contient toutes les tâches d'arrière-plan et utilise la fonction concurrency::when_all pour définir le travail à effectuer une fois qu'elles sont terminées. Étant donné que la méthode TripOptimizerImpl::RetrieveLocationsAsync fait partie de la tâche de fond globale, cette chaîne des tâches d'opérations asynchrones ne bloque pas l'application principale.

Les informations sur le fonctionnement de la classe HttpRequest sont présentées dans la section Définition des fonctionnalités HTTP dans ce document.

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

La méthode TripOptimizerImpl::RetrieveLocationsAsync utilise également la méthode concurrency::task::then pour traiter chaque réponse de Bing Maps dès qu'elle arrive. La méthode task::then crée une tâche de continuation. Il s'agit d'une tâche qui s'exécute après la fin de la tâche précédente. L'appel à when_all à la fin de la méthode TripOptimizerImpl::RetrieveLocationsAsync attend que toutes les tâches et leurs tâches de continuation soient terminées. Pour plus d'informations sur les tâches et les continuations dans C++, consultez Parallélisme des tâches.

L'API Bing Maps REST retourne des données XML. La méthode TripOptimizerImpl::ProcessLocation charge des informations d'emplacement pour un emplacement de flux de données XML fourni. Cette méthode utilise XmlDocument::SelectSingleNodeNS pour traiter l'objet XmlDocument fourni. Cet exemple montre comment la méthode TripOptimizerImpl::ProcessLocation récupère le code de réponse pour la demande :

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

Important

Assurez-vous d'utiliser la notation text() lorsque vous utilisez XmlDocument::SelectSingleNodeNS pour sélectionner des nœuds de texte à partir d'un document XML.

La méthode TripOptimizerImpl::ProcessLocation lève une exception si une erreur se produit. Dans cet exemple, TripOptimizerImpl::ProcessLocation lève l'exception Platform::NullReferenceException si le document XML ne contient pas les données attendues. Cette erreur n'étant pas récupérable, le composant n'intercepte pas cette exception. Par conséquent, si une exception se produit, elle est passée au gestionnaire d'erreurs dans l'application principale.

La méthode TripOptimizerImpl::ProcessLocation lit le nombre total de ressources à partir du flux XML. Une ressource fait référence à une correspondance possible pour un nom d'emplacement. Par exemple, si vous spécifiez « Pittsburgh », les Bing Maps peuvent retourner « Pittsburgh, PA », « Pittsburgh, ON » et « Pittsburgh, GA » comme possibilités. Ensuite pour chaque ressource, TripOptimizerImpl::ProcessLocation renseigne l'objet Node correspondant à l'aide de la latitude et de la longitude de l'emplacement. Si plusieurs ressources sont retournées, la méthode TripOptimizerImpl::ProcessLocation ajoute le nœud à 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 ne contient pas d'éléments, la méthode TripOptimizerImpl::InternalOptimizeTripAsync passe à la deuxième phase, qui consiste à récupérer les données d'itinéraire à partir de Bing Maps.

Pour plus d'informations sur le service d'emplacements Bing Maps, consultez la rubrique sur les API d'emplacements.

Hh699891.collapse_all(fr-fr,VS.110).gifPhase 2 : Récupération des données d'itinéraire

Dans la deuxième phase, la méthode TripOptimizerImpl::InternalOptimizeTripAsync récupère des données d'itinéraire à partir de Bing Maps. Les données d'itinéraire sont récupérées parce que l'algorithme d'optimisation de voyage requiert la distance entre chaque ensemble de points dans le graphique. Rappelez-vous qu'un bord connecte deux nœuds dans le graphique et que chaque nœud contient un emplacement.

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

La méthode TripOptimizerImpl::RetrieveRoutesAsync suit un modèle semblable à la méthode TripOptimizerImpl::RetrieveLocationsAsync pour récupérer des données d'itinéraire à partir de 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(&params->RequestsPending);
        }));
    }

    return tasks;
}

La méthode TripOptimizerImpl::ProcessRoute suit un modèle qui ressemble à la méthode TripOptimizerImpl::ProcessLocation pour charger des données XML. La différence réside dans le fait que la méthode TripOptimizerImpl::ProcessRoute charge les informations d'itinéraire dans un objet Edge pour une paire d'emplacements de voyage.

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

Une fois que la méthode TripOptimizerImpl::RetrieveRoutesAsync traite toutes les informations d'itinéraire, la méthode TripOptimizerImpl::InternalOptimizeTripAsync exécute la dernière phase, qui consiste à exécuter l'optimisation de l'itinéraire.

Pour plus d'informations sur le service d'itinéraires Bing Maps, consultez la rubrique sur les API d'itinéraires.

Hh699891.collapse_all(fr-fr,VS.110).gifPhase 3 : Calcul de l'itinéraire optimisé

Dans la troisième phase, la méthode TripOptimizerImpl::InternalOptimizeTripAsync optimise l'itinéraire pour la distance globale la plus courte traversée. Elle appelle la fonction AntSystem::OptimizeRoute, qui utilise des nœuds et des informations sur les bords, ainsi que d'autres paramètres (tels que les entrées dans un algorithme d'optimisation de colonies de fourmis) pour calculer un voyage optimisé.

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

Au retour de la fonction AntSystem::OptimizeRoute, la méthode TripOptimizerImpl::InternalOptimizeTripAsync fait pivoter l'ordre pour faire correspondre l'entrée d'utilisateur. En d'autres termes, elle garantit que la première entrée de l'utilisateur est la première de l'itinéraire optimisé.

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

La méthode TripOptimizerImpl::InternalOptimizeTripAsync crée ensuite des vecteurs parallèles qui contiennent des informations d'emplacement (latitude et longitude) et des noms complets. Ces vecteurs sont contenus dans un objet Platform::Collections::Map. (Map est l'implémentation C++ pour l'interface Windows::Foundation::Collections::IMap<K, V>. De même, Platform::Collections::Vector est l'implémentation C++ pour l'interface Windows::Foundation::Collections::IVector<T>.) L'application principale utilise les données d'emplacement pour consulter le mappage et les noms d'emplacement dans le cadre d'instructions pas à pas.

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

[Haut]

Définition des fonctionnalités HTTP

Le composant C++ définit la classe HttpRequest pour traiter les requêtes HTTP. Cette classe utilise l'interface IXMLHTTPRequest2 pour traiter les requêtes HTTP. L'interface IXMLHTTPRequest2 prend en charge les opérations asynchrones uniquement. Pour que l'appelant puisse plus facilement utiliser ces opérations asynchrones, la méthode HttpRequest::GetAsync retourne un objet concurrency::task<std::wstring>. Cet objet de tâche contient la réponse HTTP en tant que chaîne.

IXMLHTTPRequest2 prenant uniquement en charge les opérations asynchrones, vous devez fournir un objet IXMLHTTPRequest2Callback lorsque vous demandez des données à partir d'un serveur HTTP. Le fichier HttpRequest.cpp définit la classe HttpRequestStringCallback, qui hérite de cette interface et en implémente les méthodes.

Une partie importante de cette implémentation est l'utilisation de concurrency::task_completion_event. Cette classe permet à la classe HttpReader de créer une tâche qui est définie une fois qu'une autre tâche asynchrone est terminée. Cette classe est utile lorsque vous devez composer des objets task avec des opérations asynchrones qui s'effectuent via des rappels. Une fois l'opération de téléchargement terminée, la méthode HttpRequestStringCallback::OnResponseReceived définit l'événement d'achèvement.

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

Par conséquent, la méthode HttpRequestStringCallback::OnError définit l'événement d'achèvement lorsqu'une erreur se produit. Dans ce cas, le code d'erreur et la chaîne vide constituent le résultat de la tâche.

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

La méthode HttpRequest::GetAsync appelle HttpRequest::DownloadAsync. La méthode HttpRequest::DownloadAsync ouvre la demande asynchrone et crée un objet HttpRequestStringCallback. Elle crée ensuite un objet task qui se termine une fois l'événement d'achèvement de la tâche de l'objet HttpRequestStringCallback terminé. Cet objet task utilise une continuation pour libérer l'objet HttpRequestStringCallback une fois l'événement d'achèvement de tâche terminé.

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

Pour plus d'informations sur la manière dont la classe TripOptimizerImpl utilise les données XML résultantes pour calculer l'itinéraire optimisé, consultez Flux de travail du composant dans ce document. Pour obtenir des exemples d'utilisation de IXMLHTTPRequest2, consultez Quickstart: Connecting using XML HTTP Request (IXHR2) et Procédure pas à pas : connexion à l'aide de tâches et de la requête HTTP XML (IXHR2).

[Haut]

Calcul de l'itinéraire optimal

L'algorithme principal qui exécute le calcul d'itinéraire est défini dans AntSystem.h et AntSystem.cpp. Le calcul d'itinéraire est semblable au problème du représentant commercial. L'objectif du problème du représentant commercial consiste à prendre une collection d'emplacements et la distance entre chaque paire d'emplacements, puis de calculer l'itinéraire le plus court qui passe une seule fois par chaque emplacement. Il est généralement difficile de résoudre le problème du représentant commercial, car il faut calculer tous les itinéraires possibles afin de trouver l'itinéraire optimal. L'algorithme d'optimisation de colonies de fourmis est une approche métaheuristique visant à résoudre cette classe des problèmes. Il est semblable au comportement des fourmis lorsqu'elles recherchent rapidement un itinéraire optimisé. Bien que cet algorithme ne trouve pas forcément les itinéraires les plus courts pour un ensemble donné d'emplacements, il trouve souvent l'itinéraire le plus court ou suffisamment court pour les besoins d'un voyage.

Les fichiers AntSystem.h et d'AntSystem.cpp définissent l'espace de noms AntSystem. Cet espace de noms ne contient aucune dépendance sur Windows Runtime et n'utilise pas C++/CX. AntSystem.h définit LatLong, Node et les structures Edge. Il définit également la fonction OptimizeRoute.

La structure LatLong représente la latitude et la longitude d'un point sur une carte.

// 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 structure Node représente un nœud dans un graphique. Elle stocke le nom, la latitude et la longitude d'un emplacement. Elle stocke également tous les autres noms provenant du service 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;
};

La structure Edge connecte deux nœuds et contient la distance entre eux. Elle contient également des données qui sont utilisées par l'algorithme d'optimisation de colonies de fourmis.

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

Le composant C++ crée un objet Node pour chaque emplacement du voyage et un objet Edge pour chaque paire d'emplacements. Une fois toutes les informations requises à partir des services Web Bing Maps collectées, il appelle OptimizeRoute pour calculer l'itinéraire optimal.

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

Par souci de concision, cette documentation ne décrit pas l'algorithme d'optimisation de colonies de fourmis en détail. Pour plus d'informations, consultez AntSystem.cpp dans le code source.

Toutefois, un aspect important de l'implémentation de l'algorithme est l'utilisation de l'accès concurrentiel. L'algorithme d'optimisation de colonies de fourmis exécute plusieurs itérations de trois étapes de base : autoriser chaque fourmi à parcourir le graphique, évaporer les phéromones, puis autoriser chaque fourmi à suive ces mêmes étapes pour revenir au point de départ. La première étape, consistant à autoriser chaque fourmi à parcourir le graphique, peut être exécutée en parallèle car chaque fourmi agit de manière indépendante. Cette étape ne contient ni donnée partagée ni calculs dépendants.

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

Pour plus d'informations sur les algorithmes parallèles (tels que parallel_for_each), consultez Algorithmes parallèles.

Notes

L'utilisation de l'indicateur parallel n'est pas obligatoire dans l'implémentation. Il est fourni en tant qu'option dans l'interface utilisateur, afin que vous puissiez plus facilement tester un calcul parallèle par rapport à un calcul séquentiel.

[Haut]

Gestion de l'annulation

Les interfaces IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> et IAsyncOperationWithProgress<TResult, TProgress> fournissent chacune une méthode Cancel qui vous permet d'annuler l'opération asynchrone. En C++, ces méthodes Cancel annulent un jeton d'annulation associé à l'opération asynchrone. Vous pouvez associer l'annulation de tâche aux méthodes Windows Runtime Cancel de deux façons. Vous pouvez tout d'abord définir la fonction de travail que vous passez à create_async pour prendre un objet concurrency::cancellation_token. Lorsque la méthode Cancel est appelée, ce jeton d'annulation est annulé et les règles normales d'annulation s'appliquent. Si vous ne fournissez pas d'objet cancellation_token, l'objet task sous-jacent en définit un implicitement. Définissez un objet cancellation_token lorsque vous devez répondre à l'annulation de manière coopérative dans la fonction de travail. Pour plus d'informations sur ce mécanisme d'annulation, consultez Création d'opérations asynchrones en C++ pour les applications Windows Store.

L'annulation se produit lorsque l'utilisateur clique sur le bouton Cancel dans l'application JavaScript ou si une erreur irrécupérable a lieu. II est important que l'interface utilisateur reste réactive pendant la tâche d'optimisation notamment pour permettre à l'utilisateur d'annuler. L'annulation ne se produit pas immédiatement. Le composant C++ utilise concurrency::cancellation_token_source et concurrency::cancellation_token pour signaler l'annulation et parfois pour la vérifier. Le composant C++ implémente l'annulation sur le maximum de niveau de granularité grossière possible, mais tâche toujours de permettre à l'annulation de se produire dans le délai imparti. Du point de vue des performances, une vérification trop fréquente d'annulation n'est pas bénéfique pour l'application. En fait, les performances peuvent être impactées si le temps passé à vérifier l'annulation est supérieur au temps passé à exécuter le travail.

Le composant C++ vérifie l'annulation de deux façons. D'abord, la tâche de continuation qui se produit après chaque phase d'optimisation appelle concurrency::task::get pour déterminer l'annulation. La méthode task::get lève une exception qui s'est produite pendant la tâche, y compris task_canceled si l'annulation se produit. (Dans le cas de when_all, le runtime sélectionne une des exceptions si plusieurs tâches sont levées.) Étant donné que vous devez observer toutes les exceptions de tâche qui se produisent, la fonction observe_all_exceptions est définie pour observer toutes les exceptions qui se sont produites dans les tâches fournies à l'algorithme when_all. L'exemple suivant illustre la vérification de l'annulation une fois les emplacements extraits de Bing Maps, mais avant l'extraction des itinéraires.

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

Après l'appel à observe_all_exceptions, l'exception d'origine est à nouveau levée afin qu'elle puisse être gérée par le code qui dépend de cette tâche.

La partie suivante illustre les observe_all_exceptions. Il y a itération au sein de chaque objet task de la collection fournie et la méthode task::get permet de déterminer des erreurs. Comme il est prévu de lever à nouveau une des exceptions ultérieurement, des blocs vides catch permettent d'indiquer que l'exception a été observée et gérée.

// 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 deuxième façon dont le composant vérifie l'annulation consiste à appeler la méthode concurrency::cancellation_token::is_canceled. L'algorithme d'optimisation de voyage (la fonction AntSystem::OptimizeRoute) vérifie l'annulation de cette façon toutes les 100 millisecondes.

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

Notes

Nous pourrions avoir utilisé la première technique, qui consiste à appeler is_task_cancellation_requested et cancel_current_task, au lieu d'appeler cancellation_token::is_canceled. Toutefois, cancel_current_task doit être appelé à partir d'un objet task. Étant donné qu'en théorie vous pouvez utiliser cette implémentation pour appeler la fonction AntSystem::OptimizeRoute depuis une task ou d'une autre partie du code, la méthode d'annulation directe des jetons pour plus de flexibilité est utilisée. Si cette fonction devait être appelée à partir du code qui n'utilise pas les tâches, vous pourriez passer concurrency::cancellation_token::none pour le paramètre cancellationToken. Le jeton none ne peut jamais être annulé.

La section Définition des fonctionnalités HTTP explique comment la classe HttpRequestStringCallback utilise task_completion_event pour composer des opérations asynchrones qui s'effectuent via des rappels avec des objets task. De même, pour prendre en charge l'annulation, la classe HttpRequestStringCallback utilise la méthode concurrency::cancellation_token::register_callback pour stocker une fonction de rappel qui est appelée lorsque le jeton d'annulation est annulée. Cette technique est utile parce que l'interface IXMLHTTPRequest2 exécute le travail asynchrone qui n'est pas sous notre contrôle. Lorsque le jeton d'annulation est annulé, la fonction de rappel interrompt la requête HTTP et définit l'événement d'achèvement de tâche.

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 retourne un objet de concurrency::cancellation_token_registration qui identifie l'inscription du rappel. Le destructeur de la classe HttpRequest utilise cet objet d'inscription pour annuler l'inscription de la fonction de rappel. Nous vous recommandons de toujours annuler l'enregistrement de votre rappel lorsque vous n'en avez plus besoin pour garantir que tous les objets sont valides lorsqu'une fonction de rappel est appelée.

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

Dans le cas d'une erreur irrécupérable, toutes les tâches restantes sont annulées. Par exemple, si un document XML ne peut pas être traité, l'opération globale est annulée et l'exception est à nouveau levée.

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 classe TripOptimizerImpl définit un objet concurrency::cancellation_token_source parce que l'annulation est initialisée par cette classe. Pour activer à la fois le bouton Cancel et le code interne permettant d'annuler les tâches, la classe TripOptimizerImpl appelle la méthode concurrency::cancellation_token_source::create_linked_source. Cette source de jeton d'annulation associée permet à la fois à l'application JavaScript et à la classe TripOptimizerImpl d'annuler le même jeton d'annulation, mais à partir de différents objets cancellation_token_source.

// Perform the operation asynchronously.
return create_async([this, params](progress_reporter<String^> reporter, 
    cancellation_token cancellationToken) -> task<IMap<String^, IVector<String^>^>^>
{
    // Create a linked source for cancellation.
    // This enables both the caller (through the returned 
    // IAsyncOperationWithProgress object) and this class to set
    // the same cancellation token.
    m_cancellationTokenSource = 
        cancellation_token_source::create_linked_source(cancellationToken);

Pour plus d'informations sur le fonctionnement de l'annulation dans la bibliothèque de modèles parallèles, consultez Annulation dans la bibliothèque de modèles parallèles.

[Haut]

Migration à partir d'ActiveX

Pour plus d'informations sur la migration de la version ActiveX de l'optimiseur de voyage Bing Maps vers une application Windows Store, consultez Migration du code existant dans l'exemple de l'optimiseur de voyage Bing Maps.

[Haut]

Étapes suivantes

Pour plus d'informations sur la façon dont le composant Windows Runtime C++ interagit avec le composant JavaScript, consultez Interopérabilité entre JavaScript et C++ dans l'exemple de l'optimiseur de voyage Bing Maps.

[Haut]

Voir aussi

Concepts

Interopérabilité entre JavaScript et C++ dans l'exemple de l'optimiseur de voyage Bing Maps

Utilisation de JavaScript dans l'exemple de l'optimiseur de voyage Bing Maps

Autres ressources

Développement de l'optimiseur de voyage Bing Maps, une application du Windows Store en JavaScript et C++