Condividi tramite


Utilizzo di C++ nell'esempio di utilità di ottimizzazione dei viaggi di Bing Mappe

In questo documento viene descritto il componente C++ dell'utilità di ottimizzazione dei viaggi di Bing Mappe. Il componente C++ utilizza codice nativo puro per implementare la logica dell'ottimizzazione dell'itinerario e il codice di Estensioni componenti Visual C++ (C++/CX) per l'interazione con altri componenti di Windows Runtime.

Nota

Per ulteriori informazioni su C++/CX, le estensioni del linguaggio disponibili per un'app di Windows Store in C++, vedi Riferimenti al linguaggio Visual C++ (C++/CX).

Per informazioni su come creare un componente C++ di base di Windows Runtime, vedi Procedura dettagliata: creazione di un componente Windows Runtime di base in C++ e chiamata da JavaScript.

La parte C++ dell'applicazione viene scritta come libreria a collegamento dinamico (DLL) di Windows Runtime. La libreria fornisce questa funzionalità:

  • Recuperare la latitudine e la longitudine di ogni località dal servizio Web Bing Mappe. Questa operazione utilizza l'interfaccia REST (Representational State Transfer) di Bing Mappe.

  • Recuperare la distanza tra ogni possibile coppia di punti nel viaggio. Anche questa operazione utilizza l'interfaccia REST di Bing Mappe.

  • Eseguire l'ottimizzazione dell'itinerario. Questa operazione utilizza l'algoritmo di ottimizzazione ACO (Ant Colony Optimization) e l'elaborazione parallela per calcolare in modo efficiente l'itinerario ottimizzato.

Queste operazioni vengono eseguite in modo asincrono o in background. In questo modo l'interfaccia utente rimane attiva durante il processo di ottimizzazione.

Di seguito sono riportate alcune funzionalità e tecnologie chiave utilizzate dal componente C++:

Nota

Il codice di esempio che corrisponde a questo documento è disponibile nella pagina relativa all'esempio di utilità di ottimizzazione dei viaggi di Bing Mappe.

In questo documento

  • Convenzioni nel codice

  • Organizzazione dei file

  • Creazione di classi TripOptimizerImpl e TripOptimizer

  • Flusso di lavoro del componente

    • Fase 1: recupero dei dati sulla località

    • Fase 2: recupero dei dati sull'itinerario

    • Fase 3: calcolo dell'itinerario ottimizzato

  • Definizione di funzionalità HTTP

  • Calcolo dell'itinerario ottimale

  • Gestione dell'annullamento

  • Migrazione da ActiveX

  • Passaggi successivi

Convenzioni nel codice

Oltre a utilizzare Windows Runtime per creare il componente di Windows Store, il componente C++ utilizza convenzioni di codice moderne, ad esempio puntatori intelligenti e gestione delle eccezioni.

Windows Runtime è un'interfaccia di programmazione che puoi utilizzare per creare app di Windows Store che funzionano solo in un ambiente con sistema operativo attendibile. Tali app utilizzano funzioni autorizzate, tipi di dati e dispositivi e sono distribuite da Windows Store. Windows Runtime è rappresentato dall'interfaccia applicativa binaria ABI (Application Binary Interface). L'interfaccia ABI è un contratto binario sottostante che rende disponibili le API di Windows Runtime ai linguaggi di programmazione come Visual C++.

Per semplificare la scrittura di app che utilizzano Windows Runtime, Microsoft fornisce le estensioni del linguaggio a Visual C++ che supportano in modo specifico Windows Runtime. Molte di queste estensioni del linguaggio sono simili alla sintassi del linguaggio C++/CLI. Tuttavia, invece di puntare a Common Language Runtime (CLR), le app native utilizzano questa sintassi per puntare a Windows Runtime. Il modificatore a forma di accento circonflesso (^) è una parte importante di questa nuova sintassi poiché abilita l'eliminazione automatica di oggetti di runtime per mezzo del conteggio dei riferimenti. Anziché chiamare metodi quali AddRef e Release per gestire la durata di un oggetto Windows Runtime, il runtime elimina l'oggetto quando nessun altro componente vi fa riferimento, ad esempio quando lascia l'ambito o si impostano tutti i riferimenti su nullptr. Un'altra parte importante di Visual C++ per la creazione di app di Windows Store è la parola chiave ref new. Utilizza ref new anziché new per creare oggetti Windows Runtime con conteggio dei riferimenti. Per ulteriori informazioni, vedi la pagina relativa a oggetti e ref new di Windows Runtime.

Importante

È necessario utilizzare solo ^ e ref new quando si creano oggetti di Windows Runtime o componenti di Windows Runtime. Puoi utilizzare la sintassi C++ standard per scrivere codice dell'applicazione principale che non utilizza Windows Runtime.

Nota

L'utilità di ottimizzazione dei viaggi di Bing Maps utilizza ^ con Microsoft::WRL::ComPtr, std::shared_ptr e std::unique_ptr per gestire oggetti con allocazione heap e ridurre al minimo le perdite di memoria. Consigliamo di utilizzare ^ per gestire la durata delle variabili di Windows Runtime, Microsoft::WRL::ComPtr per gestire la durata delle variabili COM e shared_ptr o unique_ptr per gestire la durata di tutti gli altri oggetti C++ allocati nell'heap.

Per ulteriori informazioni sulla programmazione moderna in C++, vedi Digitare di nuovo a C++ (C++ moderno). Per ulteriori informazioni sulle estensioni del linguaggio disponibili in un'app in Windows Store C++, vedi Riferimenti al linguaggio Visual C++ (C++/CX).

[All'inizio]

Organizzazione dei file

Nell'elenco seguente viene brevemente descritto il ruolo di ciascun file di codice sorgente che fa parte del componente C++.

  • AntSystem.h, AntSystem.cpp
    Definisce l'algoritmo di ottimizzazione ACO e le relative strutture di dati di supporto.

  • HttpRequest.h, HttpRequest.cpp
    Definisce la classe HttpRequest, una classe di supporto per l'esecuzione di richieste HTTP asincrone.

  • pch.h, pch.cpp
    Intestazione precompilata per il progetto.

  • TripOptimizer.h, TripOptimizer.cpp
    Definisce la classe TripOptimizer, che funge da interfaccia tra l'app e la logica principale dei componenti.

  • TripOptimizerImpl.h, TripOptimizerImpl.cpp
    Definisce la classe TripOptimizerImpl, che determina la logica principale del componente.

[All'inizio]

Creazione di classi TripOptimizerImpl e TripOptimizer

Il componente C++ contiene una classe Windows Runtime, TripOptimizerComponent::TripOptimizer, che può interfacciarsi con altri componenti di Windows Runtime. Nell'utilità di ottimizzazione dei viaggi di Bing Mappe questa classe si interfaccia con la parte JavaScript dell'app. Poiché il componente C++ è scritto come DLL, può anche essere utilizzato con altre app. La classe TripOptimizer definisce solo i metodi che comunicano con altri componenti di Windows Runtime. I dettagli di implementazione vengono gestiti dalla classe TripOptimizerImpl. Abbiamo scelto questo modello per incapsulare meglio l'interfaccia pubblica e separarla dai dettagli di implementazione. Per ulteriori informazioni su questo modello, vedi Pimpl per l'incapsulamento della fase di compilazione (C++ moderno).

// Defines the TripOptimizer class. This class interfaces between the app
// and the implementation details.
public ref class TripOptimizer sealed 
{
public:
    TripOptimizer();

    // Optimizes a trip as an asynchronous process.
    Windows::Foundation::IAsyncOperationWithProgress<
        Windows::Foundation::Collections::IMap<
            Platform::String^, 
            Windows::Foundation::Collections::IVector<Platform::String^>^>^, 
        Platform::String^>^ OptimizeTripAsync(
            Windows::Foundation::Collections::IVector<Platform::String^>^ waypoints, 
            Platform::String^ travelMode, 
            Platform::String^ optimize,
            Platform::String^ bingMapsKey, 
            double alpha, double beta, double rho,
            unsigned int iterations, bool parallel);

private:
    ~TripOptimizer();
    // Defines implementation details of the optimization routine.
    std::unique_ptr<Details::TripOptimizerImpl> m_impl;
};

Importante

Quando crei una classe di componente di Windows Runtime che può essere condivisa con altri componenti esterni, verifica di utilizzare le parole chiave sealed e public. Per ulteriori informazioni su come creare un componente Windows Runtime in C++ che può essere chiamato da un'app di Windows Store, vedi Creazione di componenti Windows Runtime in C++.

Il metodo TripOptimizer::OptimizeTripAsync è il modo in cui un'applicazione comunica con il componente C++. Questo metodo avvia la sequenza di attività che calcolano il viaggio ottimizzato. Il chiamante utilizza il valore restituito per monitorare lo stato di avanzamento e il completamento dell'attività di ottimizzazione. Viene anche utilizzato per annullare l'operazione. L'annullamento è illustrato nella sezione Gestione dell'annullamento più avanti in questo documento.

Il metodo TripOptimizer::OptimizeTripAsync rinvia alla classe TripOptimizerImpl l'esecuzione dell'azione. TripOptimizerImpl viene illustrato in modo dettagliato più avanti in questo 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);
}

[All'inizio]

Flusso di lavoro del componente

Il metodo TripOptimizer::OptimizeTripAsync avvia la sequenza di operazioni che calcolano il viaggio ottimizzato. Questo metodo funziona in modo asincrono per consentire all'app di rimanere attiva. Per abilitare il comportamento asincrono, questo metodo restituisce Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>. Un componente di Windows Runtime che chiama questo metodo può utilizzare l'oggetto per ottenere il valore restituito quando è disponibile. Questa interfaccia consente inoltre al chiamante di monitorare lo stato di avanzamento dell'operazione e ricevere eventuali errori che si verificano.

Ogni linguaggio abilitato per Windows Runtime (C++, JavaScript e così via) ha una modalità propria di creazione di un'operazione asincrona. In C++ puoi utilizzare la funzione concurrency::create_async. Questa funzione restituisce un oggetto IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> o IAsyncOperationWithProgress<TResult, TProgress>. Il tipo restituito dipende dalla firma dell'oggetto funzione passato. Ad esempio, poiché il metodo TripOptimizerImpl::InternalOptimizeTripAsync accetta un oggetto concurrency::progress_reporter come parametro e restituisce un valore non void, create_async restituisce IAsyncOperationWithProgress<TResult, TProgress>. Se questo metodo restituisse void, create_async restituirebbe IAsyncActionWithProgress<TProgress>. Per ulteriori informazioni su create_async e sul relativo funzionamento con Windows 8, vedi Creazione di operazioni asincrone in C++ per le applicazioni Windows Store.

Nel codice seguente viene illustrato il metodo 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'oggetto progress_reporter comunica i messaggi relativi allo stato di avanzamento al chiamante. L'oggetto cancellation_token consente al componente di rispondere alle richieste di annullamento. L'annullamento è illustrato nella sezione Gestione dell'annullamento più avanti in questo documento. Per ulteriori informazioni sul funzionamento della parte JavaScript dell'app con questa operazione asincrona restituita da TripOptimizer::OptimizeTripAsync, vedi Interazione tra JavaScript e C++ nell'utilità di ottimizzazione dei viaggi di Bing Mappe.

La funzione lavoro fornita a create_async in TripOptimizer::OptimizeTripAsync restituisce un oggetto task. Potete restituire un valore, T, o un'attività, task<T>, da create_async. In questo caso restituiamo task<T> in modo da non dover attendere il risultato delle attività in background. Facciamo invece in modo che il runtime recuperi il risultato quando è disponibile e lo passi al chiamante. Vi consigliamo di attenervi a questo modello quando possibile.

Il file TripOptimizer.cpp definisce la funzione di supporto task_from_result che restituisce un oggetto task che viene immediatamente completato con il risultato fornito. Questa funzione è utile quando scrivi una funzione che restituisce task e la funzione restituisce in anticipo un risultato specifico.

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

Nella figura seguente viene illustrato il flusso delle operazioni che si verificano quando un componente esterno chiama TripOptimizer::OptimizeTripAsync per avviare il processo di ottimizzazione.

Flusso di componenti C++

Il metodo TripOptimizerImpl::InternalOptimizeTripAsync chiama il metodo TripOptimizerImpl::OptimizeTripAsync come parte dell'operazione asincrona. Il metodo TripOptimizerImpl::CreateGraph chiama TripOptimizerImpl::InternalOptimizeTripAsync per creare un grafico che rappresenta il viaggio. Ogni località è rappresentata da un nodo e ogni coppia di nodi è connessa da un bordo. Un nodo contiene informazioni su una località, ad esempio il nome, la latitudine e la longitudine. Un bordo indica la distanza tra una coppia di nodi.

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

Il metodo TripOptimizerImpl::InternalOptimizeTripAsync esegue l'ottimizzazione del viaggio in tre fasi. Nella prima fase il metodo recupera i dati sulla località da Bing Mappe. Nella seconda fase recupera le informazioni di itinerario tra ogni possibile coppia di punti del viaggio. Nella terza fase esegue l'algoritmo di ottimizzazione del viaggio. Ogni fase funge da continuazione dell'attività della fase precedente. Le continuazioni consentono di eseguire una o più attività dopo il completamento di un'altra attività. Le continuazioni sono descritte in modo dettagliato più avanti in questo documento. Nota tuttavia che, poiché una continuazione viene eseguita in background, è necessario archiviare le variabili condivise tra le attività in modo da consentire alle attività di accedervi in un secondo momento. La classe TripOptimizerImpl definisce la struttura OptimizeTripParams. La struttura contiene gli input del metodo TripOptimizerImpl::InternalOptimizeTripAsync e le variabili condivise tra le attività che costituiscono l'intera operazione.

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

Il metodo TripOptimizerImpl::OptimizeTripAsync crea una struttura OptimizeTripParams tramite un oggetto std::shared_ptr e la passa a ognuna delle continuazioni nella catena di attività.

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

Nota

Avremmo potuto creare queste variabili come membri diretti della classe TripOptimizerImpl. Fornendo una struttura di parametri, tuttavia, associamo più chiaramente lo stato di un'operazione di ottimizzazione del viaggio alla relativa azione. Se utilizzassimo variabili membro, sarebbe più difficile supportare funzionalità quali l'esecuzione di più ottimizzazioni di viaggio contemporaneamente.

Hh699891.collapse_all(it-it,VS.110).gifFase 1: recupero dei dati sulla località

Nella prima fase il metodo TripOptimizerImpl::InternalOptimizeTripAsync recupera dati sulla località da Bing Mappe. I dati sulla località vengono recuperati per consentire all'utente di verificare che Bing Mappe disponga dell'input corretto. Se ad esempio specifichi "Pittsburgh" Bing Mappe non è in grado di capire se intendi "Pittsburgh, PA", "Pittsburgh, ON" o "Pittsburgh, GA". In un momento successivo, il componente C++ deve recuperare la distanza tra ogni coppia di località. Se la località è ambigua, è quindi necessario visualizzare nell'interfaccia utente tutte le località possibili per consentire all'utente di selezionare quella corretta o immetterne una nuova. La variabile OptimizeTripParams::UnresolvedLocations tiene traccia delle località non risolte. Se il metodo TripOptimizerImpl::RetrieveLocationsAsync compila il vettore con dei valori, TripOptimizerImpl::InternalOptimizeTripAsync li restituisce.

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

Il metodo TripOptimizerImpl::RetrieveLocationsAsync utilizza la classe concurrency::task per l'elaborazione delle richieste HTTP come attività in background. Poiché la classe HttpRequest funziona in modo asincrono, TripOptimizerImpl::RetrieveLocationsAsync contiene tutte le attività in background e utilizza la funzione concurrency::when_all per definire le operazioni da eseguire quando vengono completate. Poiché il metodo TripOptimizerImpl::RetrieveLocationsAsync fa parte dell'attività in background globale, questa catena di attività di operazioni asincrone non blocca l'applicazione principale.

I dettagli sul funzionamento della classe HttpRequest sono illustrati nella sezione Definizione della funzionalità HTTP in questo 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;
}

Il metodo TripOptimizerImpl::RetrieveLocationsAsync utilizza anche il metodo concurrency::task::then per elaborare ogni risposta da Bing Mappe quando arriva. Il metodo task::then crea un'attività di continuazione, ovvero un'attività che viene eseguita al termine dell'attività precedente o antecedente. La chiamata a when_all alla fine del metodo TripOptimizerImpl::RetrieveLocationsAsync attende il completamento di tutte le attività e delle relative attività di continuazione. Per ulteriori informazioni sulle attività e sulle continuazioni in C++, vedi Parallelismo delle attività.

L'API REST di Bing Mappe restituisce dati XML. Il metodo TripOptimizerImpl::ProcessLocation carica informazioni sulla località per una località nel flusso di dati XML fornito. Questo metodo utilizza XmlDocument::SelectSingleNodeNS per elaborare l'oggetto XmlDocument fornito. In questo esempio viene illustrato in che modo il metodo TripOptimizerImpl::ProcessLocation recupera il codice di risposta per la richiesta:

// Move to response code.
// Report an error and return if the status code is 
// not 200 (OK).
xmlNode = xmlDocument->SelectSingleNodeNS(L"/d:Response/d:StatusCode/text()", ns);
if (xmlNode == nullptr) throw ref new NullReferenceException("Failed to parse status code from HTTP response");

Importante

Assicurati di utilizzare la notazione text() quando utilizzi XmlDocument::SelectSingleNodeNS per selezionare i nodi di testo da un documento XML.

Il metodo TripOptimizerImpl::ProcessLocation genera un'eccezione se si verifica un errore. In questo esempio, TripOptimizerImpl::ProcessLocation genera Platform::NullReferenceException se il documento XML non contiene i dati previsti. Poiché questo errore è irreversibile, il componente non intercetta questa eccezione. Se, pertanto, si verifica un'eccezione, viene passata al gestore errori nell'applicazione principale.

Il metodo TripOptimizerImpl::ProcessLocation legge il numero di risorse totali dal flusso XML. Una risorsa fa riferimento a una corrispondenza possibile per un nome di località. Se ad esempio specifichi "Pittsburgh", Bing Mappe potrebbe restituire "Pittsburgh, PA", "Pittsburgh, ON" e "Pittsburgh, GA" come possibilità. Per ogni risorsa, TripOptimizerImpl::ProcessLocation popola quindi l'oggetto Node corrispondente utilizzando la latitudine e la longitudine della località. Se vengono restituite più risorse, il metodo TripOptimizerImpl::ProcessLocation aggiunge il nodo alla variabile OptimizeTripParams::UnresolvedLocations.

// If there is only a single name, set it as the resolved name and 
// location.
if (node->Names.size() == 1)
{
    node->ResolvedLocation = node->Locations.front();
    node->ResolvedName = node->Names.front();
}
// Otherwise, add the node to the list of unresolved locations.
else if (node->ResolvedName.length() == 0)
{
    unresolvedLocations.push_back(node);
}

Se la variabile OptimizeTripParams::UnresolvedLocations non contiene alcun elemento, il metodo TripOptimizerImpl::InternalOptimizeTripAsync passa alla seconda fase, ovvero al recupero dei dati di itinerario da Bing Mappe.

Per ulteriori informazioni sul servizio di Bing Mappe per le località, vedi la pagina relativa alle API di località.

Hh699891.collapse_all(it-it,VS.110).gifFase 2: recupero dei dati sull'itinerario

Nella seconda fase il metodo TripOptimizerImpl::InternalOptimizeTripAsync recupera dati relativi all'itinerario da Bing Mappe. I dati relativi all'itinerario vengono recuperati poiché l'algoritmo di ottimizzazione del viaggio richiede la distanza tra ogni set di punti nel grafico. Tieni presente che un bordo collega due nodi nel grafico e ciascun nodo contiene una località.

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

Il metodo TripOptimizerImpl::RetrieveRoutesAsync segue uno schema analogo al metodo TripOptimizerImpl::RetrieveLocationsAsync per recuperare i dati relativi all'itinerario di Bing Mappe.

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

Il metodo TripOptimizerImpl::ProcessRoute segue uno schema analogo al metodo TripOptimizerImpl::ProcessLocation per caricare dati XML. La differenza è data dal fatto che il metodo TripOptimizerImpl::ProcessRoute carica le informazioni relative all'itinerario in un oggetto Edge per una coppia di località di viaggio.

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

Dopo l'elaborazione di tutte le informazioni relative al viaggio eseguita dal metodo TripOptimizerImpl::RetrieveRoutesAsync, il metodo TripOptimizerImpl::InternalOptimizeTripAsync passa alla fase finale, ovvero all'esecuzione dell'ottimizzazione dell'itinerario.

Per ulteriori informazioni sul servizio di Bing Mappe per gli itinerari, vedi la pagina relativa alle API di itinerari.

Hh699891.collapse_all(it-it,VS.110).gifFase 3: calcolo dell'itinerario ottimizzato

Nella terza fase, il metodo TripOptimizerImpl::InternalOptimizeTripAsync ottimizza l'itinerario per la minore distanza globale percorsa. Chiama la funzione AntSystem::OptimizeRoute, che utilizza nodi e informazioni dei bordi con altri parametri, ad esempio gli input per l'algoritmo di ottimizzazione ACO, per calcolare il viaggio ottimizzato.

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

Quando viene restituito il risultato della funzione AntSystem::OptimizeRoute, il metodo TripOptimizerImpl::InternalOptimizeTripAsync ruota l'ordine in base all'input dell'utente. In altre parole, assicura che la prima voce dell'utente sia la prima voce nell'itinerario ottimizzato.

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

Il metodo TripOptimizerImpl::InternalOptimizeTripAsync crea quindi i vettori paralleli che contengono dati sulla località (latitudine e longitudine) e nomi visualizzati. Questi vettori sono contenuti in un oggetto Platform::Collections::Map. Map è l'implementazione di C++ per l'interfaccia Windows::Foundation::Collections::IMap<K, V>. Analogamente, Platform::Collections::Vector è l'implementazione di C++ per l'interfaccia Windows::Foundation::Collections::IVector<T>. L'app principale utilizza i dati sulla località per visualizzare la mappa e i nomi di località come parte delle indicazioni delle svolte.

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

[All'inizio]

Definizione di funzionalità HTTP

Il componente C++ definisce la classe HttpRequest per elaborare le richieste HTTP. Questa classe utilizza l'interfaccia IXMLHTTPRequest2 per elaborare le richieste HTTP. L'interfaccia IXMLHTTPRequest2 supporta solo le operazioni asincrone. Per semplificare l'utilizzo delle operazioni asincrone da parte del chiamante, il metodo HttpRequest::GetAsync restituisce un oggetto concurrency::task<std::wstring>. Questo oggetto attività utilizza la risposta HTTP come stringa.

Poiché IXMLHTTPRequest2 supporta solo le operazioni asincrone, dovete fornire un oggetto IXMLHTTPRequest2Callback quando richiedete dati da un server HTTP. Il file HttpRequest.cpp definisce la classe HttpRequestStringCallback, che eredita da questa interfaccia e ne implementa i metodi.

Una parte importante di questa implementazione è l'utilizzo di concurrency::task_completion_event. Questa classe consente alla classe HttpReader di creare un'attività impostata al completamento di un'altra attività asincrona. Questa classe è utile quando è necessario comporre oggetti task insieme a operazioni asincrone che vengono completate tramite callback. Al completamento dell'operazione di download, il metodo HttpRequestStringCallback::OnResponseReceived imposta l'evento di completamento.

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

Di conseguenza, il metodo HttpRequestStringCallback::OnError imposta l'evento di completamento quando si verifica un errore. In questo caso, come risultato l'attività restituisce il codice di errore e la stringa vuota.

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

Il metodo HttpRequest::GetAsync chiama HttpRequest::DownloadAsync. Il metodo HttpRequest::DownloadAsync apre la richiesta asincrona e crea un oggetto HttpRequestStringCallback. Crea quindi un oggetto task che viene completato al termine dell'evento di completamento dell'attività dell'oggetto HttpRequestStringCallback. Questo oggetto task utilizza una continuazione per rilasciare l'oggetto HttpRequestStringCallback al termine dell'evento di completamento dell'attività.

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

Per informazioni sulla modalità in cui la classe TripOptimizerImpl utilizza i dati XML risultanti per calcolare l'itinerario ottimizzato, vedi Flusso di lavoro del componente in questo documento. Per altri esempi di utilizzo di IXMLHTTPRequest2, vedi Quickstart: Connecting using XML HTTP Request (IXHR2) e Procedura dettagliata: connessione tramite attività e richiesta HTTP XML (IXHR2).

[All'inizio]

Calcolo dell'itinerario ottimale

L'algoritmo principale che esegue il calcolo dell'itinerario è definito in AntSystem.h e in AntSystem.cpp. Il calcolo dell'itinerario è simile al problema del commesso viaggiatore. L'obiettivo di tale problema è prendere una raccolta di località e la distanza tra ogni coppia di località e calcolare l'itinerario più breve per visitare ogni località una sola volta. Il problema del commesso viaggiatore è tradizionalmente difficile da risolvere perché richiede il calcolo di ogni possibile itinerario per trovare l'itinerario ottimale. L'algoritmo di ottimizzazione ACO è un approccio metaeuristico per risolvere questa classe di problemi. Modella il comportamento delle formiche per trovare rapidamente un itinerario ottimizzato. Sebbene non sia garantito che gli itinerari prodotti da questo algoritmo siano i più brevi per un determinato insieme di località, l'algoritmo trova spesso l'itinerario più breve o sufficientemente breve ai fini del viaggio.

I file AntSystem.cpp e AntSystem.h definiscono lo spazio dei nomi AntSystem. Questo spazio dei nomi non contiene dipendenze in Windows Runtime e pertanto non utilizza C++/CX. AntSystem.h definisce le strutture LatLong, Node e Edge. Definisce inoltre la funzione OptimizeRoute.

La struttura LatLong rappresenta la latitudine e la longitudine di un punto su una mappa.

// 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 struttura Node rappresenta un nodo in un grafico. Contiene il nome, la latitudine e la longitudine di una località. Contiene inoltre qualsiasi nome alternativo fornito dal servizio di Bing Mappe.

// 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 struttura Edge connette due nodi e contiene la distanza tra di essi. Contiene inoltre i dati utilizzati dall'algoritmo di ottimizzazione ACO.

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

Il componente C++ crea un oggetto Node per ogni località del viaggio e un oggetto Edge per ogni coppia di località. Dopo avere raccolto tutte le informazioni necessarie dai servizi Web di Bing Mappe, chiama OptimizeRoute per calcolare l'itinerario ottimale.

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

Per ragioni di brevità, in questa documentazione non viene descritto in modo dettagliato l'algoritmo di ottimizzazione ACO. Per maggiori dettagli, vedi AntSystem.cpp nel codice sorgente.

Un aspetto importante dell'implementazione dell'algoritmo è tuttavia l'utilizzo della concorrenza. L'algoritmo di ottimizzazione ACO esegue diverse iterazioni di tre passaggi di base: permettere che ogni formica percorra il grafico, evaporare feromone, quindi permettere che ogni formica ripercorra i propri passi per tornare al punto di partenza. Il primo passaggio, in cui si permette che ogni formica percorra il grafico, può essere eseguito in parallelo poiché ogni formica agisce in modo indipendente. Questo passaggio non contiene dati condivisi o calcoli dipendenti.

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

Per ulteriori informazioni sugli algoritmi paralleli come parallel_for_each, vedi Algoritmi paralleli.

Nota

L'utilizzo del flag parallel non è una parte obbligatoria dell'implementazione. Viene fornito come opzione nell'interfaccia utente per consentirvi di sperimentare più facilmente il calcolo parallelo rispetto a quello seriale.

[All'inizio]

Gestione dell'annullamento

Le interfacce IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> e IAsyncOperationWithProgress<TResult, TProgress> forniscono ciascuna un metodo Cancel che consente di annullare l'operazione asincrona. In C++ questi metodi Cancel annullano un token di annullamento associato all'operazione asincrona. Puoi connettere l'annullamento dell'attività ai metodi Cancel di Windows Runtime in due modi. Innanzitutto, puoi definire la funzione lavoro passata a create_async in modo che accetti un oggetto concurrency::cancellation_token. Quando viene chiamato il metodo Cancel, questo token di annullamento viene annullato e vengono applicate le normali regole di annullamento. Se non fornisci un oggetto cancellation_token, l'oggetto task sottostante ne definisce uno in modo implicito. Definisci un oggetto cancellation_token quando devi rispondere in modo cooperativo all'annullamento nella funzione lavoro. Per ulteriori informazioni su questo meccanismo di annullamento, vedi Creazione di operazioni asincrone in C++ per le applicazioni Windows Store.

L'annullamento ha luogo quando l'utente sceglie il pulsante Cancel nell'applicazione JavaScript o si verifica un errore irreversibile. Uno dei motivi dell'importanza di mantenere reattiva l'interfaccia utente durante l'attività di ottimizzazione è consentire all'utente di annullare un'operazione. L'annullamento non si verifica immediatamente. Il componente C++ utilizza concurrency::cancellation_token_source e concurrency::cancellation_token per segnalare l'annullamento e verificare occasionalmente l'annullamento. Il componente C++ implementa l'annullamento su un livello con granularità il più grossolana possibile, pur cercando di consentire un annullamento tempestivo. Dal punto di vista delle prestazioni, verificare troppo spesso la presenza di annullamenti non costituisce un vantaggio per l'app. In effetti, le prestazioni possono risultare compromesse se si trascorre più tempo a verificare la presenza di annullamenti che a eseguire il lavoro.

Il componente C++ verifica la presenza di annullamenti in due diversi modi. Innanzitutto, l'attività di continuazione che ha luogo dopo ogni fase di ottimizzazione chiama concurrency::task::get per verificare l'annullamento. Il metodo task::get genera qualsiasi eccezione che si verifica durante l'attività, incluso task_canceled se si è verificato l'annullamento. Nel caso di when_all, il runtime sceglie una delle eccezioni se vengono generate più attività). Poiché è necessario osservare tutte le eccezioni di attività che si verificano, definiamo la funzione observe_all_exceptions in modo da osservare tutte le eccezioni che si sono verificate nelle attività fornite all'algoritmo when_all. Nell'esempio seguente viene illustrata la verifica della presenza di annullamento dopo il recupero di località da Bing Mappe, ma prima del recupero degli itinerari.

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

Dopo la chiamata a observe_all_exceptions, rigeneriamo l'eccezione originale in modo che possa essere gestita dal codice che dipende dall'attività.

Di seguito è illustrato observe_all_exceptions. Scorre ogni oggetto task nella raccolta fornita e utilizza il metodo task::get per verificare l'assenza di errori. Poiché pianifichiamo di rigenerare successivamente una delle eccezioni, utilizziamo blocchi catch vuoti per indicare che l'eccezione è stata osservata e gestita.

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

Il secondo modo in cui il componente verifica la presenza di annullamento è tramite la chiamata al metodo concurrency::cancellation_token::is_canceled. L'algoritmo di ottimizzazione del viaggio (funzione AntSystem::OptimizeRoute) verifica la presenza di annullamento in questo modo ogni 100 millisecondi.

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

Nota

Avremmo potuto utilizzare la prima tecnica, ovvero la chiamata a is_task_cancellation_requested e cancel_current_task, anziché chiamare cancellation_token::is_canceled. Tuttavia, è necessario chiamare cancel_current_task da un oggetto task. Poiché in teoria è possibile utilizzare questa implementazione per chiamare la funzione AntSystem::OptimizeRoute da task o da un'altra parte del codice, utilizziamo direttamente i token di annullamento per maggiore flessibilità. Se questa funzione dovesse essere chiamata da codice che non utilizza attività, potresti passare concurrency::cancellation_token::none per il parametro cancellationToken. Non è mai possibile annullare il token none.

Nella sezione Definizione della funzionalità HTTP viene descritto in che modo la classe HttpRequestStringCallback utilizza task_completion_event per comporre operazioni asincrone che vengono completate tramite callback con oggetti task. In modo analogo, per supportare l'annullamento, la classe HttpRequestStringCallback utilizza il metodo concurrency::cancellation_token::register_callback per registrare una funzione di callback che viene chiamata all'annullamento del token di annullamento. Questa tecnica è utile perché l'interfaccia IXMLHTTPRequest2 esegue il lavoro asincrono all'esterno del controllo. Quando viene annullato il token di annullamento, la funzione di callback interrompe la richiesta HTTP e imposta l'evento di completamento dell'attività.

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 restituisce un oggetto concurrency::cancellation_token_registration che identifica la registrazione di callback. Il distruttore della classe HttpRequest utilizza questo oggetto di registrazione per annullare la registrazione della funzione di callback. Vi consigliamo di annullare sempre la registrazione del callback quando non è più necessario per garantire la validità di tutti gli oggetti quando viene chiamata una funzione di callback.

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

In caso di errore irreversibile, tutte le attività rimanenti vengono annullate. Ad esempio, se un documento XML non può essere elaborato, l'operazione globale viene annullata e l'eccezione viene generata nuovamente.

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 definisce un oggetto concurrency::cancellation_token_source poiché l'annullamento viene avviato tramite questa classe. Per consentire l'annullamento delle attività tramite il pulsante Cancel e il codice interno, la classe TripOptimizerImpl chiama il metodo concurrency::cancellation_token_source::create_linked_source. Questa origine collegata del token di annullamento consente all'app JavaScript e alla classe TripOptimizerImpl di annullare lo stesso token di annullamento, ma da oggetti cancellation_token_source diversi.

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

Per ulteriori informazioni sul funzionamento dell'annullamento nella libreria PPL (Parallel Patterns Library), vedi Annullamento nella libreria PPL.

[All'inizio]

Migrazione da ActiveX

Per informazioni sulla modalità di migrazione dalla versione ActiveX dell'utilità di ottimizzazione dei viaggi di Bing Maps a un'app di Windows Store, vedi Migrazione del codice esistente nell'utilità di ottimizzazione dei viaggi di Bing Mappe.

[All'inizio]

Passaggi successivi

Per informazioni sull'interazione del componente C++ di Windows Runtime con il componente JavaScript, vedi Interazione tra JavaScript e C++ nell'utilità di ottimizzazione dei viaggi di Bing Mappe.

[All'inizio]

Vedere anche

Concetti

Interazione tra JavaScript e C++ nell'utilità di ottimizzazione dei viaggi di Bing Mappe

Utilizzo di JavaScript nell'esempio di utilità di ottimizzazione dei viaggi di Bing Mappe

Altre risorse

Sviluppo dell'utilità di ottimizzazione dei viaggi di Bing Mappe, un'app di Windows Store in JavaScript e C++