共用方式為


在 Bing 地圖服務路線最佳化程式範例中使用 C++

本文件說明 Bing 地圖服務路線最佳化程式的 C++ 元件。C++元件會使用純原生程式碼來實作路線最佳化的邏輯,並使用 Visual C++ 元件擴充功能 (C++/CX) 程式碼做為與其他 Windows 執行階段元件互動的介面。

注意事項注意事項

如需以 C++ 撰寫之 Windows 市集應用程式可用的語言擴充功能 (C++/CX) 的詳細資訊,請參閱 Visual C++ 語言參考 (C++/CX)

如需如何建立基本 C++ Windows 執行階段元件的詳細資訊,請參閱逐步解說:以 C++ 建立基本 Windows 執行階段元件,然後從 JavaScript 呼叫該元件

應用程式的 C++ 部分會撰寫成 Windows 執行階段動態連結程式庫 (DLL)。這個程式庫提供下列功能:

  • 從 Bing Maps Web 服務擷取每個位置的緯度與經度。這項作業會使用 Bing 地圖具像狀態傳輸 (REST) 介面。

  • 擷取旅程中所有各兩點之間的旅行距離。這項作業也會使用 Bing 地圖 REST 介面。

  • 執行路線最佳化。這項作業會使用蟻群最佳化演算法與平行處理,以有效計算最佳化的路線。

這些作業會以非同步的方式執行,或是在背景中執行。如此在最佳化處理期間,使用者介面便仍能繼續回應。

以下是 C++ 元件所使用的部分主要功能與技術:

注意事項注意事項

如需本文件的對應範例程式碼,請參閱 Bing 地圖服務路線最佳化程式範例

本文內容

  • 程式碼慣例

  • 檔案組織

  • 建立 TripOptimizer 與 TripOptimizerImpl 類別

  • 元件工作流程

    • 階段 1:擷取位置資料

    • 階段 2:擷取路線資料

    • 階段 3:計算最佳路線

  • 定義 HTTP 功能

  • 計算最佳路線

  • 處理取消作業

  • 從 ActiveX 移轉

  • 後續步驟

程式碼慣例

除了使用 Windows 執行階段建立 Windows 市集 元件之外,C++ 元件還會使用現代程式碼慣例 (例如智慧型指標和例外狀況處理)。

Windows 執行階段程式設計介面可讓您建立只能在值得信任的作業系統環境中執行的 Windows 市集應用程式。這類應用程式使用授權的函式、資料型別及裝置,且從 Windows 市集 發佈。Windows 執行階段是以「應用程式二進位介面」(Application Binary Interface,ABI) 為代表。ABI 是讓 Visual C++ 等程式設計語言能夠使用 Windows 執行階段 應用程式開發介面的基礎二進位合約。

爲了方便人撰寫使用 Windows 執行階段的應用程式,Microsoft 提供了專門在支援 Windows 執行階段的 Visual C++ 語言擴充功能。這些語言擴充功能有很多都類似 C++/CLI 語言的語法。不過,原生應用程式使用此語法來以 Windows 執行階段為目標,而不是以 Common Language Runtime (CLR) 為目標。帽型 (^) 修飾詞是這種新語法的重要部分,因為它可利用參考計數來自動刪除執行階段物件。此執行階段管理 Windows 執行階段物件的存留期的方式並不是透過呼叫 AddRefRelease 等方法,而是刪除沒有其他元件在參考的物件 (例如因為物件不在範圍內,或是您將所有參考都設為 nullptr)。Visual C++ 在建立 Windows 市集應用程式時的另一個重點是 ref new 關鍵字。請使用 ref new 而不是 new 來建立參考計數的 Windows 執行階段物件。如需詳細資訊,請參閱 Windows 執行階段物件及 ref new (英文)。

重要

當您建立 Windows 執行階段物件或建立 Windows 執行階段元件時,您只需要使用 ^ref new。當您撰寫的應用程式程式碼未使用 Windows 執行階段時,您可以使用標準 C++ 語法。

注意事項注意事項

Bing 地圖服務路線最佳化程式使用 ^ 並搭配 Microsoft::WRL::ComPtrstd::shared_ptrstd::unique_ptr 來管理堆積配置的物件,並使記憶體流失情況降到最低。建議您使用 ^ 管理 Windows 執行階段變數的存留期、使用 Microsoft::WRL::ComPtr 管理 COM 變數的存留期,並使用 shared_ptrunique_ptr 管理其他所有在堆積上配置之 C++ 物件的存留期。

如需現代 C++ 程式設計的詳細資訊,請參閱歡迎回到 C++ (現代的 C++)。如需 C++ Windows 市集應用程式可用的語言擴充功能的詳細資訊,請參閱 Visual C++ 語言參考 (C++/CX)

[頂端]

檔案組織

下列清單將簡短描述 C++ 元件中的每個原始程式碼檔所扮演的角色。

  • AntSystem.h、AntSystem.cpp
    定義蟻群最佳化演算法及其支援的資料結構。

  • HttpRequest.h、HttpRequest.cpp
    定義 HttpRequest 類別,這個類別是執行非同步 HTTP 要求所需的協助程式類別。

  • pch.h、pch.cpp
    替專案預先編譯的標頭。

  • TripOptimizer.h、TripOptimizer.cpp
    定義 TripOptimizer 類別,這個類別會做為應用程式與核心元件邏輯之間的介面。

  • TripOptimizerImpl.h、TripOptimizerImpl.cpp
    定義 TripOptimizerImpl 類別,這個類別會定義元件的核心邏輯。

[頂端]

建立 TripOptimizer 與 TripOptimizerImpl 類別

C++ 元件包含 Windows 執行階段類別 TripOptimizerComponent::TripOptimizer,可與其他 Windows 執行階段元件互動。在Bing 地圖服務路線最佳化程式中,這個類別會做為與應用程式的 JavaScript 部分互動的介面 (C++ 元件是寫成 DLL 的形式,因此也可用於其他應用程式)。TripOptimizer 類別僅定義與其他 Windows 執行階段元件通訊的方法。實作細節是由 TripOptimizerImpl 類別處理。我們之所以選擇這種模式,是爲了將公用介面進行更好的封裝,並將它與實作細節隔開。如需此模式的詳細資訊,請參閱Pimpl 的編譯時期封裝 (現代的 C++)

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

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

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

重要

當您建立可共用給其他外部元件的 Windows 執行階段元件類別時,請記得使用 publicsealed 關鍵字。如需如何使用可從 Windows 市集應用程式呼叫之 C++ 建立 Windows 執行階段元件的詳細資訊,請參閱在 C++ 中建立 Windows 執行階段元件

TripOptimizer::OptimizeTripAsync 方法是應用程式與 C++ 元件通訊的方式。此方法會啟動計算最佳化行程的工作序列。呼叫端會使用傳回值監視最佳化工作的進度和完成狀態。它也可用來取消作業。取消作業將在本文件稍後的處理取消作業中說明。

TripOptimizer::OptimizeTripAsync 方法會參考其 TripOptimizerImpl 類別來執行動作。本文件稍後將詳細說明 TripOptimizerImpl

// Optimizes a trip as an asynchronous process.
IAsyncOperationWithProgress<IMap<String^, IVector<String^>^>^, String^>^ 
TripOptimizer::OptimizeTripAsync(
    IVector<String^>^ waypoints, 
    String^ travelMode, 
    String^ optimize,
    String^ bingMapsKey, 
    double alpha, double beta, double rho,
    unsigned int iterations, bool parallel)
{
    // Forward to implementation.    
    return m_impl->OptimizeTripAsync(waypoints,travelMode, optimize, bingMapsKey,
        alpha, beta, rho, iterations, parallel);
}

[頂端]

元件工作流程

TripOptimizer::OptimizeTripAsync 方法會啟動一系列計算最佳旅程的作業。這個方法會以非同步的方式運作,讓應用程式仍能繼續回應。爲了能夠以非同步的方式運作,這個方法會傳回 Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress>。呼叫這個方法的 Windows 執行階段元件可使用該物件在有傳回值出現時取得傳回值。此介面也可讓呼叫者監視作業的進度以及收到任何發生的錯誤。

每個支援 Windows 執行階段的語言 (C++、JavaScript 等) 都自有一套建立非同步作業的方式。在 C++ 中,您可以使用 concurrency::create_async 函式。此函式會傳回 IAsyncActionIAsyncActionWithProgress<TProgress>IAsyncOperation<TResult>IAsyncOperationWithProgress<TResult, TProgress> 物件。傳回型別取決於您傳遞給函式物件的簽章。例如,TripOptimizerImpl::InternalOptimizeTripAsync 方法採用 concurrency::progress_reporter 物件做為其參數,並傳回非 void 值,因此 create_async 會傳回 IAsyncOperationWithProgress<TResult, TProgress>。如果此方法傳回 void,則 create_async 會傳回 IAsyncActionWithProgress<TProgress>。如需 create_async 及如何將它用於 Windows 8 的詳細資訊,請參閱使用 C++ 為 Windows 市集應用程式建立非同步作業

下列程式碼顯示 TripOptimizerImpl::OptimizeTripAsync 方法。

// Optimizes a trip as an asynchronous process.
IAsyncOperationWithProgress<IMap<String^, IVector<String^>^>^, String^>^ 
    TripOptimizerImpl::OptimizeTripAsync(
    IVector<String^>^ waypoints, 
    String^ travelMode,
    String^ optimize,
    String^ bingMapsKey,
    double alpha, double beta, double rho,
    unsigned int iterations, bool parallel)
{
    // Copy inputs to a OptimizeTripParams structure.
    auto params = make_shared<OptimizeTripParams>();
    for (auto waypoint : waypoints)
    {
        params->Waypoints.push_back(waypoint->Data());
    }
    params->TravelMode = wstring(travelMode->Data());
    params->Optimize = wstring(optimize->Data());
    params->BingMapsKey = UriEncode(bingMapsKey->Data());
    params->Alpha = alpha;
    params->Beta = beta;
    params->Rho = rho;
    params->Iterations = iterations;
    params->Parallel = parallel;

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

        // Perform the trip optimization.
        return InternalOptimizeTripAsync(params, cancellationToken, reporter)
            .then([this](task<IMap<String^, IVector<String^>^>^> previousTask) -> IMap<String^, IVector<String^>^>^
        {
            try
            {
                return previousTask.get();
            }
            catch (const task_canceled&)
            {
                return nullptr;
            }
        });
    });
}

progress_reporter 物件會傳達進度訊息給呼叫者。cancellation_token 物件可讓元件回應取消要求 (取消作業將在本文件的處理取消作業中說明)。如需應用程式的 JavaScript 部分如何使用 TripOptimizer::OptimizeTripAsync 所傳回的這個非同步作業的詳細資訊,請參閱 Bing 地圖服務路線最佳化程式範例中 JavaScript 和 C++ 之間的交互作業

TripOptimizer::OptimizeTripAsync 中提供給 create_async 的工作函式會傳回 task 物件。您可以從 create_async 傳回值 T 或工作 task<T>。我們決定傳回 task<T>,如此即無需等待背景工作出現結果,而是讓執行階段在結果出現時加以擷取,然後將該結果傳遞給呼叫者。建議您盡量採用這個模式。

TripOptimizer.cpp 檔案會定義 task_from_result 協助程式函式,以傳回會隨著所提供結果立即完成的 task 物件。當您撰寫會傳回 task 的函式,而該函式很快便會傳回特定結果時,這個函式會很有用。

// Creates a task that completes with the provided result.
template <typename Result>
task<Result> task_from_result(Result result)
{
    return create_task([result]() -> Result { return result; });
}

下圖顯示當外部元件呼叫 TripOptimizer::OptimizeTripAsync 以啟動最佳化處理序時所產生的作業流程。

C++ 元件流程

TripOptimizerImpl::OptimizeTripAsync 方法在非同步作業期間會呼叫 TripOptimizerImpl::InternalOptimizeTripAsync 方法。TripOptimizerImpl::InternalOptimizeTripAsync 方法會呼叫 TripOptimizerImpl::CreateGraph,以建立代表旅程的圖形。每個位置分別由一個節點代表,而兩個節點之間以邊緣相連接。節點包含位置的相關資訊,例如其名稱、緯度與經度等。邊緣則包含兩個節點之間的旅行距離。

// Creates the graph of objects that represents the trip topography.
void TripOptimizerImpl::CreateGraph(
    const vector<wstring>& waypoints, 
    vector<shared_ptr<Node>>& nodes,
    vector<shared_ptr<Edge>>& edges)
{
    //
    // Create a Node object for each waypoint in the array.
    // Each element of the waypoints array contains a string that represents 
    // a location (for example, "Space Needle, WA").
    //

    for (const wstring& waypoint : waypoints)
    {
        // Add a Node object to the collection.
        nodes.push_back(make_shared<Node>(waypoint));
    }

    //
    // Create edges that form a fully-connected graph.
    //

    // Connect every node to every other node.
    for (auto iter = begin(nodes); iter != end(nodes); ++iter)
    {
        auto node1 = *iter;
        for_each(iter + 1, end(nodes), [this, &node1, &edges](shared_ptr<Node> node2)
        {
            // Create edge pair.
            edges.push_back(make_shared<Edge>(node1, node2));
        });
    }
}

TripOptimizerImpl::InternalOptimizeTripAsync 方法會分三階段執行旅程最佳化。在第一個階段中,這個方法會從 Bing 地圖服務擷取位置資料。在第二個階段中,這個方法會擷取旅程中所有各兩點之間的路線資訊。在第三個階段中,這個方法會執行旅程最佳化演算法。每個階段都在接續前一個階段的工作。接續工作可讓您在一項工作完成後接著執行一或多項工作。接續工作將在本文件稍後有更詳細的說明。但請注意,由於接續工作是在背景中執行,因此您必須儲存要共用給後續工作的變數,供後續的工作存取。TripOptimizerImpl 類別會定義 OptimizeTripParams 結構。此結構包含 TripOptimizerImpl::InternalOptimizeTripAsync 方法所需的輸入,以及整體作業的各構成工作所共用的變數。

// Holds variables that are used throughout the trip optimization process.
// We create this stucture so that common parameters can be easily passed among
// task continuations. 
struct OptimizeTripParams
{
    // 
    // The inputs to OptimizeTripAsync

    std::vector<std::wstring> Waypoints;
    std::wstring TravelMode;
    std::wstring Optimize;
    std::wstring BingMapsKey;
    double Alpha;
    double Beta;
    double Rho;
    unsigned long Iterations;
    bool Parallel;

    //
    // Timer variables

    // The following times are sent as part of a progress message.
    // The overall time.
    ULONGLONG TotalTime;
    // The overall time and the spent performing HTTP requests.
    ULONGLONG HttpTime;   
    // The time spent performing the optimization algorithm.
    ULONGLONG SimulationTime;

    //
    // Location graph.

    // A collection of Node objects. There is one Node object for each location.
    std::vector<std::shared_ptr<AntSystem::Node>> Nodes;

    // A collection of Edge objects. There are
    // (n * (n - 1) / 2) edges, where n is the number of nodes.
    std::vector<std::shared_ptr<AntSystem::Edge>> Edges;

    // The number of pending HTTP requests for the current batch.
    long RequestsPending;

    // Holds the unresolved locations for the first phase of the optimization process.
    Concurrency::concurrent_vector<std::shared_ptr<AntSystem::Node>> UnresolvedLocations;
};

TripOptimizerImpl::OptimizeTripAsync 方法會建立 OptimizeTripParams 結構 (透過使用 std::shared_ptr 物件),並將其傳遞給工作鏈結中的每個接續工作。

// Copy inputs to a OptimizeTripParams structure.
auto params = make_shared<OptimizeTripParams>();
for (auto waypoint : waypoints)
{
    params->Waypoints.push_back(waypoint->Data());
}
params->TravelMode = wstring(travelMode->Data());
params->Optimize = wstring(optimize->Data());
params->BingMapsKey = UriEncode(bingMapsKey->Data());
params->Alpha = alpha;
params->Beta = beta;
params->Rho = rho;
params->Iterations = iterations;
params->Parallel = parallel;
注意事項注意事項

我們原也可以將這些變數建立為 TripOptimizerImpl 類別的直接成員。但是透過提供參數結構,我們可更清楚地建立旅程最佳化作業的狀態與動作之間的關聯性。如果使用成員變數,將更難以支援如同時執行多個旅程最佳化之類的功能。

Hh699891.collapse_all(zh-tw,VS.110).gif階段 1:擷取位置資料

在第一個階段中,TripOptimizerImpl::InternalOptimizeTripAsync 方法會從 Bing 地圖服務擷取位置資料。擷取位置資料可讓使用者確認 Bing 地圖服務具有正確的輸入。例如,如果您指定 "Pittsburgh",Bing 地圖服務並不知道您所指的是 "Pittsburgh, PA"、"Pittsburgh, ON" 還是 "Pittsburgh, GA"。C++ 元件之後必須擷取每兩個位置之間的旅行距離。因此,如果有任何位置不明確,UI 中即必須顯示所有可能的位置,以供使用者選取位置或輸入新位置。OptimizeTripParams::UnresolvedLocations 變數會追蹤懸而未決的位置。如果 TripOptimizerImpl::RetrieveLocationsAsync 方法在此向量中填入任何值, TripOptimizerImpl::InternalOptimizeTripAsync 即會傳回這些值。

//
// Phase 1: Retrieve the location of each waypoint.
//

//
params->HttpTime = GetTickCount64();

// Report progress.
reporter.report("Retrieving locations (0% complete)...");

auto tasks = RetrieveLocationsAsync(params, cancellationToken, reporter);

// Move to the next phase after all current tasks complete.
return when_all(begin(tasks), end(tasks)).then(
    [=](task<void> t) -> task<IMap<String^, IVector<String^>^>^> 
{
    // The PPL requires that all exceptions be caught and handled.
    // If any task in the collection errors, ensure that all errors in the entire
    // collection are observed at least one time.
    try
    {
        // Calling task.get will cause any exception that occurred 
        // during the task to be thrown.
        t.get();
    }
    catch (Exception^)
    {
        observe_all_exceptions<void>(begin(tasks), end(tasks));
        // Rethrow the original exception.
        throw;
    }
    catch (const task_canceled&)
    {
        observe_all_exceptions<void>(begin(tasks), end(tasks));
        // Rethrow the original exception.
        throw;
    }

    // Report progress.
    reporter.report("Retrieving locations (100% complete)...");

    // If there are unresolved locations, return them.
    if (params->UnresolvedLocations.size() > 0)
    {
        // Parallel arrays of input names to their possible resolved names.
        Vector<String^>^ inputNames;
        Vector<String^>^ options;

        auto locations = ref new Map<String^, IVector<String^>^>();

        // For each unresolved location, add the user input name and
        // the available options to the arrays.
        for (size_t i = 0; i < params->UnresolvedLocations.size(); ++i)
        {
            auto node = params->UnresolvedLocations[i];

            // Add options.
            options = ref new Vector<String^>();
            for (size_t j = 0; j < node->Names.size(); ++j)
            {
                options->Append(ref new String(node->Names[j].c_str()));
            }
            locations->Insert(ref new String(node->InputName.c_str()), options);
        }

        return task_from_result<IMap<String^, IVector<String^>^>^>(locations);
    }

TripOptimizerImpl::RetrieveLocationsAsync 方法會使用 concurrency::task 類別,以背景工作的形式處理 HTTP 要求。由於 HttpRequest 類別會以非同步方式運作,因此 TripOptimizerImpl::RetrieveLocationsAsync 會包含所有的背景工作,並使用 concurrency::when_all 函式定義在這些工作完成後執行的工作。由於 TripOptimizerImpl::RetrieveLocationsAsync 方法是整體背景工作的一部分,因此這段非同步作業工作鏈結不會妨礙主要應用程式執行。

關於 HttpRequest 類別如何運作的詳細資訊,將在本文件的定義 HTTP 功能中說明。

// Retrieves information about the locations from the Bing Maps location service.
vector<task<void>> TripOptimizerImpl::RetrieveLocationsAsync(
    shared_ptr<OptimizeTripParams> params,
    cancellation_token cancellationToken,
    progress_reporter<String^> reporter)
{
    // Holds the tasks that process the returned XML documents.
    vector<task<void>> tasks;

    // Create HTTP requests for location information.
    auto nodes = params->Nodes;
    params->RequestsPending = static_cast<long>(params->Nodes.size()); // Used to report progress.
    for (auto node : nodes)
    {
        wstringstream uri;
        uri << L"http://dev.virtualearth.net/REST/v1/Locations?q=" 
            << UriEncode(node->InputName)
            << L"&o=xml&key=" << params->BingMapsKey;

        // Create a parent task that downloads the XML document.
        auto httpRequest = make_shared<HttpRequest>();
        auto downloadTask = httpRequest->GetAsync(
            ref new Uri(ref new String(uri.str().c_str())), 
            cancellationToken);

        // Create a continuation task that fills location information after 
        // the download finishes.
        tasks.push_back(downloadTask.then([=](task<wstring> previousTask)
        {
            (void)httpRequest;

            // Get the result to force exceptions to be thrown.
            wstring response = previousTask.get();

            try
            {
                // Create and load the XML document from the response.
                XmlDocument^ xmlDocument = ref new XmlDocument();
                auto xml = ref new String(response.c_str() + 1); // Bypass BOM.
                xmlDocument->LoadXml(xml);

                // Fill in location information.
                ProcessLocation(node, xmlDocument, params->UnresolvedLocations);
            }
            catch (Exception^)
            {
                // An error occurred. Cancel any active operations.
                m_cancellationTokenSource.cancel();
                // Rethrow the exception.
                throw;
            }

            // Report progress.
            wstringstream progress;
            progress << L"Retrieving locations (" 
                << static_cast<long>(100.0 * (params->Nodes.size() - params->RequestsPending) / params->Nodes.size())
                << L"% complete)...";
            reporter.report(ref new String(progress.str().c_str()));
            InterlockedDecrement(&params->RequestsPending);
        }));
    }

    return tasks;
}

TripOptimizerImpl::RetrieveLocationsAsync 方法也會使用 concurrency::task::then 方法,在每個來自 Bing 地圖服務的回應到達時加以處理。task::then 方法會建立「接續工作」(Continuation Task),也就是前一項工作完成後會執行的工作。在 TripOptimizerImpl::RetrieveLocationsAsync 方法結束時呼叫的 when_all,會等待所有的工作及其接續工作完成。如需 C++ 中之工作和接續工作的詳細資訊,請參閱工作平行處理原則

Bing 地圖 REST 應用程式開發介面會傳回 XML 資料。TripOptimizerImpl::ProcessLocation 方法會從所提供的 XML 資料流載入位置的位置資訊。此方法使用 XmlDocument::SelectSingleNodeNS 來處理提供的 XmlDocument 物件。此範例將說明 TripOptimizerImpl::ProcessLocation 方法如何擷取要求的回應碼:

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

重要

當您使用 XmlDocument::SelectSingleNodeNS 從 XML 文件中選取文字節點時,請務必使用 text() 標記法。

TripOptimizerImpl::ProcessLocation 方法會在錯誤發生時擲回例外狀況。在此範例中,如果 XML 文件不含預期的資料,TripOptimizerImpl::ProcessLocation 即會擲回 Platform::NullReferenceException。由於這是無法復原的錯誤,元件不會攔截此例外狀況。因此發生例外狀況時,此例外狀況將會被傳遞到主要應用程式中的錯誤處理常式。

TripOptimizerImpl::ProcessLocation 方法會讀取來自 XML 資料流的資源總數。「資源」(Resource) 是指某個位置名稱可能相符的項目。例如,如果您指定 "Pittsburgh",Bing 地圖服務可能會傳回 "Pittsburgh, PA"、"Pittsburgh, ON" 與 "Pittsburgh, GA" 做為可能值。接著,TripOptimizerImpl::ProcessLocation 會針對每項資源,使用位置的緯度與經度填入對應的 Node 物件。如果傳回多項資源,則 TripOptimizerImpl::ProcessLocation 方法會將節點加入至 OptimizeTripParams::UnresolvedLocations 變數。

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

如果 OptimizeTripParams::UnresolvedLocations 變數不含任何項目,則 TripOptimizerImpl::InternalOptimizeTripAsync 方法會進入第二階段,也就是從 Bing 地圖服務擷取路線資料。

如需 Bing 地圖位置服務的詳細資訊,請參閱位置應用程式開發介面 (英文)。

Hh699891.collapse_all(zh-tw,VS.110).gif階段 2:擷取路線資料

在第二個階段中,TripOptimizerImpl::InternalOptimizeTripAsync 方法會從 Bing 地圖擷取路線資料。之所以要擷取路線資料,是因為旅程最佳化演算法需要有圖形中每組地點之間的距離。前文提過,圖形中的兩個節點會以邊緣相連接,而每個節點分別包含一個位置。

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

TripOptimizerImpl::RetrieveRoutesAsync 方法會依循與 TripOptimizerImpl::RetrieveLocationsAsync 方法類似的模式,從 Bing 地圖擷取路線資料。

// Retrieves distance information for each pair of locations from the Bing Maps route service.
vector<task<void>> TripOptimizerImpl::RetrieveRoutesAsync(
    shared_ptr<OptimizeTripParams> params,
    cancellation_token cancellationToken,
    progress_reporter<String^> reporter)
{   
    // Holds the tasks that process the returned XML documents.
    vector<task<void>> tasks;

    // Implementation note:
    // We assume that the route from A -> B is the same *distance* as 
    // the route from B -> A. Although this enables us to make fewer HTTP route requests,
    // the final route might be slightly sub-optimal if the trip contains legs with 
    // one-way streets or the distance from A -> B differs from the distance from B -> A.
    // (However, the end route will always contain the correct turn information.)

    // Create HTTP requests for route information.
    auto edges = params->Edges;
    params->RequestsPending = static_cast<long>(edges.size()); // Used to report progress.
    for (auto edge : edges)
    {
        // Create request URL.
        LatLong pointA = edge->PointA->ResolvedLocation;
        LatLong pointB = edge->PointB->ResolvedLocation;

        wstringstream uri;
        uri << L"http://dev.virtualearth.net/REST/v1/Routes/" << params->TravelMode
            << L"?wp.0=" << pointA.Latitude << L"," << pointA.Longitude
            << L"&wp.1=" << pointB.Latitude << L"," << pointB.Longitude
            << L"&optmz=" << params->Optimize
            << L"&o=xml"
            << L"&key=" << params->BingMapsKey;

        // Create a parent task that downloads the XML document.
        auto httpRequest = make_shared<HttpRequest>();
        auto downloadTask = httpRequest->GetAsync(
            ref new Uri(ref new String(uri.str().c_str())), 
            cancellationToken);

        // Create a continuation task that fills route information after 
        // the download finishes.
        tasks.push_back(downloadTask.then([=](task<wstring> previousTask)
        {
            (void)httpRequest;

            // Get the result to force exceptions to be thrown.
            wstring response = previousTask.get();

            try
            {
                // Create and load the XML document from the response.
                XmlDocument^ xmlDocument = ref new XmlDocument();
                auto xml = ref new String(response.c_str() + 1); // Bypass BOM.
                xmlDocument->LoadXml(xml);

                // Fill in route information.
                ProcessRoute(edge, xmlDocument);
            }
            catch (Exception^)
            {
                // An error occurred. Cancel any other active downloads.
                m_cancellationTokenSource.cancel();
                // Rethrow the exception.
                throw;
            }

            // Report progress.
            wstringstream progress;
            progress << L"Retrieving routes ("
                << static_cast<long>(100.0 * (params->Edges.size() - params->RequestsPending) / params->Edges.size())
                << L"% complete)...";
            reporter.report(ref new String(progress.str().c_str()));
            InterlockedDecrement(&params->RequestsPending);
        }));
    }

    return tasks;
}

TripOptimizerImpl::ProcessRoute 方法會依循與 TripOptimizerImpl::ProcessLocation 方法類似的模式來載入 XML 資料。兩者的差異在於,TripOptimizerImpl::ProcessRoute 方法會將路線資訊載入至兩個旅行地點之間的 Edge 物件中。

//
// Update edges.

// Set travel distance.
edge->TravelDistance = travelDistance;

// Ensure that the distance is at least FLT_EPSILON.
// If the travel distance is very short, a value below FLT_EPSILON
// can result in a divide by zero error in the trip optimization algorithm.
if (edge->TravelDistance < FLT_EPSILON)
{
    edge->TravelDistance = FLT_EPSILON;
}

// Set latitude and longitude of both points.
edge->PointA->ResolvedLocation = LatLong(lat0, lon0);
edge->PointB->ResolvedLocation = LatLong(lat1, lon1);

TripOptimizerImpl::RetrieveRoutesAsync 方法處理所有的路線資訊後,TripOptimizerImpl::InternalOptimizeTripAsync 方法即會執行最後階段,也就是執行路線最佳化。

如需 Bing 地圖路線服務的詳細資訊,請參閱路線應用程式開發介面 (英文)。

Hh699891.collapse_all(zh-tw,VS.110).gif階段 3:計算最佳路線

在第三個階段中,TripOptimizerImpl::InternalOptimizeTripAsync 方法會進行路線最佳化,以找出整體旅行距離最短的路線。它會呼叫 AntSystem::OptimizeRoute 函式,以使用節點、邊緣資訊與其他參數 (例如對蟻群最佳化演算法的輸入) 計算最佳的旅程。

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

AntSystem::OptimizeRoute 函式傳回時,TripOptimizerImpl::InternalOptimizeTripAsync 方法會旋轉順序,以配合使用者的輸入。換句話說,它會確保使用者的第一個項目即為最佳路線的第一個項目。

// Create the final route.
// The optimizer returns a route that has an arbitrary starting point.
// For example, the route might look like:
// A -> B -> C -> D -> E -> A
// If our starting point was D, we want the route to look like:
// D -> E -> A -> B -> C -> D
routeIndices.pop_back();
while (routeIndices.front() != 0)
{
    routeIndices.push_back(routeIndices.front());
    routeIndices.erase(begin(routeIndices));
}
routeIndices.push_back(routeIndices.front());

接著,TripOptimizerImpl::InternalOptimizeTripAsync 方法會建立包含位置資料 (緯度與經度) 與顯示名稱的平行向量。這些向量包含在 Platform::Collections::Map 物件中。(Map 是 C++ 對 Windows::Foundation::Collections::IMap<K, V> 介面的實作。同樣的,Platform::Collections::Vector 是 C++ 對 Windows::Foundation::Collections::IVector<T> 介面的實作。)主要應用程式會使用位置資料,顯示逐步交通指示的地圖與位置名稱。

//
// Prepare the return value.
//

// Parallel arrays that hold the optimized route locations and names.
IVector<String^>^ optimizedRoute;             // {"47.620056,-122.349261", ...}
IVector<String^>^ optimizedRouteDisplayNames; // {"Space Needle, WA", ...}

optimizedRoute = ref new Vector<String^>();
optimizedRouteDisplayNames = ref new Vector<String^>();

// Fill the arrays.
size_t i = 0;
for (size_t index : routeIndices)
{
    const auto node = params->Nodes[index];

    String^ v;

    // The location is the latitude and longitude of the waypoint.
    // For example, "47.620056,-122.349261"
    wstringstream location;
    location << node->ResolvedLocation.Latitude << L',' << node->ResolvedLocation.Longitude;

    v = ref new String(location.str().c_str());
    optimizedRoute->InsertAt(static_cast<unsigned int>(i), v);

    // The display name if the resolved name of the waypoint.
    // For example, "Space Needle, WA"
    v = ref new String(node->ResolvedName.c_str());
    optimizedRouteDisplayNames->InsertAt(static_cast<unsigned int>(i), v);

    ++i;
}

// The return value.
auto finalRoute = ref new Map<String^, IVector<String^>^>();

finalRoute->Insert("locations", optimizedRoute);
finalRoute->Insert("displayNames", optimizedRouteDisplayNames);

// Compute the overall elapsed time.
params->TotalTime = GetTickCount64() - params->TotalTime;

// Report final progress.
// This message contains the overall elapsed time, the time spent performing 
// HTTP requests, and the time it took to run the simulation.
wstringstream progress;
progress << L"Loading Map. Elapsed time: "
    << params->TotalTime << L"ms (total); "
    << params->HttpTime << L"ms (HTTP); "
    << params->SimulationTime << L"ms (simulation).";
reporter.report(ref new String(progress.str().c_str()));

return task_from_result<IMap<String^, IVector<String^>^>^>(finalRoute);

[頂端]

定義 HTTP 功能

C++ 元件會定義用以處理 HTTP 要求的 HttpRequest 類別。此類別使用 IXMLHTTPRequest2 介面來處理 HTTP 要求。IXMLHTTPRequest2 介面僅支援非同步作業。為了方便呼叫者使用這些非同步作業,HttpRequest::GetAsync 方法會傳回 concurrency::task<std::wstring> 物件。此工作物件會以字串形式來保存 HTTP 回應。

由於 IXMLHTTPRequest2 僅支援非同步作業,因此當您向 HTTP 伺服器要求資料時,您必須提供 IXMLHTTPRequest2Callback 物件。HttpRequest.cpp 檔案會定義 HttpRequestStringCallback 類別,此類別繼承自這個介面,並且實作此介面的方法。

這項實作有一個很重要的部分,就是使用 concurrency::task_completion_event。此類別可讓 HttpReader 類別建立一個會在其他非同步工作完成時才設定好的工作。當您需要撰寫要與透過回呼完成的非同步作業搭配運作的 task 物件時,這個類別會很有用。當下載作業順利完成時,HttpRequestStringCallback::OnResponseReceived 方法便會設定完成事件。

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

同理,HttpRequestStringCallback::OnError 方法會在發生錯誤時設定完成事件。在此情況下,工作的結果將會是錯誤碼與空字串。

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

HttpRequest::GetAsync 方法呼叫 HttpRequest::DownloadAsyncHttpRequest::DownloadAsync 方法開啟非同步要求,並建立 HttpRequestStringCallback 物件。接著,它會建立在 HttpRequestStringCallback 物件的工作完成事件完成時執行的 task 物件。這個 task 物件會在工作完成事件完成後,使用接續工作發出 HttpRequestStringCallback 物件。

// Start a download of the specified URI using the specified method.  The returned task produces the
// HTTP response text.  The status code and reason can be read with GetStatusCode() and GetReasonPhrase().
task<wstring> HttpRequest::DownloadAsync(PCWSTR httpMethod, PCWSTR uri, cancellation_token cancellationToken,
    PCWSTR contentType, IStream* postStream, uint64 postStreamSizeToSend)
{
    // Create an IXMLHTTPRequest2 object.
    ComPtr<IXMLHTTPRequest2> xhr;
    CheckHResult(CoCreateInstance(CLSID_XmlHttpRequest, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&xhr)));

    // Create callback.
    auto stringCallback = Make<HttpRequestStringCallback>(xhr.Get(), cancellationToken);
    CheckHResult(stringCallback ? S_OK : E_OUTOFMEMORY);

    auto completionTask = create_task(stringCallback->GetCompletionEvent());

    // Create a request.
    CheckHResult(xhr->Open(httpMethod, uri, stringCallback.Get(), nullptr, nullptr, nullptr, nullptr));

    if (postStream != nullptr && contentType != nullptr)
    {
        CheckHResult(xhr->SetRequestHeader(L"Content-Type", contentType));
    }

    // Send the request.
    CheckHResult(xhr->Send(postStream, postStreamSizeToSend));

    // Return a task that completes when the HTTP operation completes. 
    // We pass the callback to the continuation because the lifetime of the 
    // callback must exceed the operation to ensure that cancellation 
    // works correctly.
    return completionTask.then([this, stringCallback](tuple<HRESULT, wstring> resultTuple)
    {
        // If the GET operation failed, throw an Exception.
        CheckHResult(std::get<0>(resultTuple));

        statusCode = stringCallback->GetStatusCode();
        reasonPhrase = stringCallback->GetReasonPhrase();

        return std::get<1>(resultTuple);
    });
}

如需 TripOptimizerImpl 類別如何使用最後產生的 XML 資料計算最佳路徑的詳細資訊,請參閱本文件的元件工作流程。如需 IXMLHTTPRequest2 使用方式的其他範例,請參閱Quickstart: Connecting using XML HTTP Request (IXHR2)逐步解說:使用工作和 XML HTTP 要求 (IXHR2) 連線

[頂端]

計算最佳路線

執行路線計算所用的核心演算法,是定義於 AntSystem.h 與 AntSystem.cpp 中。路線計算類似於業務員拜訪客戶順序的問題。業務員解決拜訪客戶順序問題的方法,在於查出所有的位置以及所有位置彼此之間的距離,然後計算只拜訪每個位置一次的最短路線。業務員拜訪客戶順序問題在過去是很難解的問題,因為必須先計算每個可能的路線,才能找出最佳路線。蟻群最佳化演算法是一種超啟發式 (Metaheuristic) 演算法,可解決這類的問題。它模仿螞蟻的行為模式,以快速找出最佳路線。雖然就經過所有特定位置而言,此演算法所產生的路線不見得最短的路線,但通常可找出夠短的旅行路線。

AntSystem.h 與 AntSystem.cpp 檔案會定義 AntSystem 命名空間。此命名空間不含 Windows 執行階段的相依性,因此不使用 C++/CX。AntSystem.h 會定義 LatLongNodeEdge 結構。它也會定義 OptimizeRoute 函式。

LatLong 結構代表地圖上某個點的緯度與經度。

// Represents the latitude and longitude of a single point on a map.
struct LatLong
{
    explicit LatLong(double latitude, double longitude) 
        : Latitude(latitude)
        , Longitude(longitude)
    {
    }

    // The coordinates of the location.
    double Latitude;
    double Longitude;
};

Node 結構代表圖形中的節點。其中包含位置的名稱、緯度與經度。此外也包含任何來自於 Bing 地圖服務的替代名稱。

// Represents a node in a graph.
struct Node 
{
    explicit Node(const std::wstring& inputName)
        : InputName(inputName)
        , ResolvedLocation(0.0, 0.0)
    {
    }

    // The name of the location as provided by the user.
    std::wstring InputName;
    // The resolved latitude and longitude of the location as provided by the 
    // Bing Maps location service.
    LatLong ResolvedLocation;
    // The resolved name of the location as provided by the 
    // Bing Maps location service.
    std::wstring ResolvedName;

    //
    // Parallel arrays of string names and latitude, longitude pairs that represent
    // all possible resolved locations for the current input name.
    // For example, if the input name is "Redmond", the Names array might contain
    // "Redmond, WA", "Redmond, OR", "Redmond, UT", and "Redmond, Australia".
    // The Locations array would contain the corresponding latitude and longitude
    // for each location.
    std::vector<std::wstring> Names;
    std::vector<LatLong> Locations;
};

Edge 結構會連接兩個節點,並包含這兩個節點之間的旅行距離。它也包含蟻群最佳化演算法所使用的資料。

// Represents an edge in a graph of Node objects.
// An Edge object connects two nodes and holds the travel distance between 
// those nodes. An Edge object also holds the amount of pheromone that 
// exists on that edge.
struct Edge
{
    explicit Edge(std::shared_ptr<Node> pointA, std::shared_ptr<Node> pointB)
        : PointA(pointA)
        , PointB(pointB)
        , Pheromone(0.0)
        , TravelDistance(-1.0)
    {
    }

    // The start node.
    std::shared_ptr<Node> PointA;
    // The end node.
    std::shared_ptr<Node> PointB;
    // The amount of pheromone on the edge.
    double Pheromone;
    // The distance from the start node to the end node.
    double TravelDistance;
};

C++ 元件會爲旅程中的每個位置建立一個 Node 物件,並且為每個位置的配對建立一個 Edge 物件。它在從 Bing Maps Web 服務收集所有必要的資訊後,便會呼叫 OptimizeRoute 以計算最佳路線。

// Optimizes the route among the given nodes for the shortest travel distance.
// This method returns indicies to the provided Node collection.
std::vector<size_t> OptimizeRoute(
    std::vector<std::shared_ptr<Node>>& nodes,
    std::vector<std::shared_ptr<Edge>>& edges,
    double alpha,
    double beta,
    double rho,
    unsigned int iterations,
    Concurrency::cancellation_token cancellationToken,
    std::function<void(unsigned int)>* progressCallback = nullptr,
    bool parallel = true);

為求簡潔,本文件並未詳細說明蟻群最佳化演算法。如需詳細資訊,請參閱 AntSystem.cpp 的原始程式碼。

但是,此演算法實作的重點之一,就是採用並行處理。蟻群最佳化演算法會反覆執行三個基本步驟數次:讓每隻螞蟻在圖形上爬行、留下費洛蒙,然後讓每隻螞蟻循自己的足跡返回起點。第一個步驟是讓每隻螞蟻在圖形上爬行,這可以平行執行,因為每隻螞蟻都是獨立行動的。這個步驟不含共用資料或相依計算。

// Perform the simulation several times.
auto startTime = GetTickCount64();
for (unsigned int i = 0; i < iterations; ++i)
{
    // Occasionally check for cancellation.
    auto time = GetTickCount64();
    if (time - startTime > 100) 
    {
        if (cancellationToken.is_canceled())
        {
            // Return the empty collection.
            return vector<size_t>();
        }
        startTime = time;
    }

    // Send progress. 
    if (progressCallback != nullptr)
    {
        (*progressCallback)(i);
    }

    // 
    // Allow each ant to perform a tour of the graph.
    // Note that this operation can be performed in parallel because each ant acts independently.
    // This step contains no shared data or dependent computations.
    if (parallel)
    {
        parallel_for_each(begin(ants), end(ants), [&](Ant& blitz)
        {
            blitz.Explore();
        });
    }
    else
    {
        for_each(begin(ants), end(ants), [&](Ant& blitz)
        {
            blitz.Explore();
        });
    }

    //
    // Evaporate pheromone.
    for_each(begin(edges), end(edges), [rho](shared_ptr<Edge> edge)
    {
        edge->Pheromone *= (1.0 - rho);
    });

    // 
    // Allow each ant to backtrack through the graph and drop pheromone on 
    // each edge.
    //
    // Note that this operation is NOT performed in parallel because 
    // the ants update the pherone value of each edge.
    // Because the backtrack operation is not relatively lengthy, the 
    // overhead that is required to synchronize access to the edges would 
    // likely outweigh any benefits of parallel processing.
    for_each(begin(ants), end(ants), [&](Ant& blitz)
    {
        blitz.Backtrack();
    });
}

如需平行演算法 (如 parallel_for_each) 的詳細資訊,請參閱平行演算法

注意事項注意事項

實作時不一定要使用 parallel 旗標。UI 中之所以提供這個選項,是為了方便您試用平行計算,看看會不會比序列計算好。

[頂端]

處理取消作業

IAsyncActionIAsyncActionWithProgress<TProgress>IAsyncOperation<TResult>IAsyncOperationWithProgress<TResult, TProgress> 介面皆提供 Cancel 方法供您取消非同步作業。在 C++中,這些 Cancel 方法會取消與非同步作業相關聯的「取消語彙基元」(Cancellation Token)。您可以透過兩種方式,將工作的取消與 Windows 執行階段 Cancel 方法相連。首先,您可以定義您傳遞給 create_async 的工作函式,使其採用 concurrency::cancellation_token 物件。呼叫 Cancel 方法時,即會取消此取消語彙基元,並套用一般取消規則。如果您未提供 cancellation_token 物件,基礎 task 物件會暗自定義一個。當您需要在工作函式中配合性地回應取消時,請定義 cancellation_token 物件。如需此取消機制的詳細資訊,請參閱使用 C++ 為 Windows 市集應用程式建立非同步作業

當使用者選擇 JavaScript 應用程式中的 Cancel 按鈕時,或發生無法復原的錯誤時,即會執行取消。使用者可進行取消,是 UI 在最佳化工作期間必須仍能繼續回應的原因之一。取消並不會立即執行。C++ 元件會使用 concurrency::cancellation_token_sourceconcurrency::cancellation_token 發出取消信號,以及偶爾檢查有無取消情形。C++ 元件會盡可能以粗略的程度來實作取消,但仍會設法讓取消及時執行。從效能的觀點來看,太常檢查有無取消情形對應用程式並沒有好處。事實上,如果檢查取消要求所耗費的時間高於執行工作的時間,效能即可能下降。

C++ 元件使用兩種不同的方式來檢查是否取消。第一個方式是透過每個最佳化階段之後發生的接續工作來呼叫 concurrency::task::get,以測試是否取消。task::get 方法會擲回任何在工作期間發生的例外狀況,包括 task_canceled (如果發生取消)。(在 when_all 的情況下,如果多個工作擲回例外狀況,則執行階段會選擇其中一個例外狀況。)由於您必須觀察發生的所有工作例外狀況,因此我們定義了 observe_all_exceptions 函式來觀察提供給 when_all 演算法的工作中所發生的所有例外狀況。下列範例將說明從 Bing 地圖服務擷取位置之後、但在擷取路線前所進行的取消檢查。

//
// Phase 2: Retrieve route information for each pair of locations.
//

// Report progress.
reporter.report("Retrieving routes (0% complete)...");

auto tasks = RetrieveRoutesAsync(params, cancellationToken, reporter);

// Move to the next phase after all current tasks complete.
return when_all(begin(tasks), end(tasks)).then(
    [=](task<void> t) -> task<IMap<String^, IVector<String^>^>^>
{
    // The PPL requires that all exceptions be caught and handled.
    // If any task in the collection errors, ensure that all errors in the entire
    // collection are observed at least one time.
    try
    {
        // Calling task.get will cause any exception that occurred 
        // during the task to be thrown.
        t.get();
    }
    catch (Exception^)
    {
        observe_all_exceptions<void>(begin(tasks), end(tasks));
        // Rethrow the original exception.
        throw;
    }
    catch (const task_canceled&)
    {
        observe_all_exceptions<void>(begin(tasks), end(tasks));
        // Rethrow the original exception.
        throw;
    }

    // Report progress.
    reporter.report("Retrieving routes (100% complete)...");

    // Record the elapsed HTTP time.
    params->HttpTime = GetTickCount64() - params->HttpTime;

呼叫 observe_all_exceptions 之後,我們會重新擲回原始例外狀況,以供相依於該工作的程式碼處理。

以下顯示 observe_all_exceptions。它會逐一查看所提供之集合中的每個 task 物件,並使用 task::get 方法來測試錯誤。由於我們想在稍後重新擲回其中一個例外狀況,因此使用空的 catch 區塊來表示已觀察及處理例外狀況。

// Observes all exceptions that occurred in all tasks in the given range.
template<class T, class InIt>
void observe_all_exceptions(InIt first, InIt last)
{
    for_each (first, last, [](task<T> t)
    {
        t.then([](task<T> previousTask)
        {
            try
            {
                previousTask.get();
            }
            catch (Exception^)
            {
                // Swallow the exception.
            }
            catch (const exception&)
            {
                // Swallow the exception.
            }
        });
    });
}

第二個方式是藉由元件呼叫 concurrency::cancellation_token::is_canceled 方法,來檢查是否取消。旅程最佳化演算法 (AntSystem::OptimizeRoute 函式) 每隔 100 毫秒就會以此方式檢查有無取消情形。

// Perform the simulation several times.
auto startTime = GetTickCount64();
for (unsigned int i = 0; i < iterations; ++i)
{
    // Occasionally check for cancellation.
    auto time = GetTickCount64();
    if (time - startTime > 100) 
    {
        if (cancellationToken.is_canceled())
        {
            // Return the empty collection.
            return vector<size_t>();
        }
        startTime = time;
    }
注意事項注意事項

我們原也可以使用第一種技術,也就是呼叫 is_task_cancellation_requestedcancel_current_task,而不是呼叫 cancellation_token::is_canceled。但要呼叫 cancel_current_task 則必須從 task 物件進行。由於理論上,您可以使用此實作,從 task 或程式碼的另一部分來呼叫 AntSystem::OptimizeRoute 函式,因此直接使用取消語彙基元會更有彈性。如果要從不使用工作的程式碼呼叫此函式,您可以在 cancellationToken 參數傳遞 concurrency::cancellation_token::none。一律不可取消 none 語彙基元。

定義 HTTP 功能 一節會說明 HttpRequestStringCallback 類別如何使用 task_completion_event 撰寫透過回呼完成的非同步作業與搭配運作的 task 物件。同樣地,為支援取消作業,HttpRequestStringCallback 類別也會使用 concurrency::cancellation_token::register_callback 方法,登錄當取消語彙基元被取消時要呼叫的回呼函式。這項技術會很有用,因為 IXMLHTTPRequest2 介面會執行不在我們的掌控中的非同步工作。當取消語彙基元被取消時,回呼函式會中止 HTTP 要求,並設定工作完成事件。

HttpRequestStringCallback(IXMLHTTPRequest2* r, cancellation_token ct) :
    request(r), cancellationToken(ct)
{
    // Register a callback function that aborts the HTTP operation when 
    // the cancellation token is canceled.
    if (cancellationToken != cancellation_token::none())
    {
        registrationToken = cancellationToken.register_callback([this]() 
        {
            if (request != nullptr) 
            {
                request->Abort();
            }
        });
    }
}

cancellation_token::register_callback 會傳回用以識別回呼登錄的 concurrency::cancellation_token_registration 物件。HttpRequest 類別的解構函式會使用此登錄物件,取消登錄回呼函式。建議您隨時取消登錄不再需要的回呼,以確保在呼叫回呼函式時,所有物件都是有效的。

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

如果是發生了無法復原的錯誤,則會取消所有其餘的工作。例如,如果無法處理某個 XML 文件,則會取消整個作業,並重新擲回例外狀況。

try
{
    // Create and load the XML document from the response.
    XmlDocument^ xmlDocument = ref new XmlDocument();
    auto xml = ref new String(response.c_str() + 1); // Bypass BOM.
    xmlDocument->LoadXml(xml);

    // Fill in location information.
    ProcessLocation(node, xmlDocument, params->UnresolvedLocations);
}
catch (Exception^)
{
    // An error occurred. Cancel any active operations.
    m_cancellationTokenSource.cancel();
    // Rethrow the exception.
    throw;
}

TripOptimizerImpl 類別會定義 concurrency::cancellation_token_source 物件,因為取消作業是透過此類別啟始。為了同時啟用 Cancel 按鈕和內部程式碼來取消工作,TripOptimizerImpl 類別會呼叫 concurrency::cancellation_token_source::create_linked_source 方法。這個連結的取消語彙基元來源可讓 JavaScript 應用程式與 TripOptimizerImpl 類別皆能取消來自不同 cancellation_token_source 物件的同一個取消語彙基元。

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

如需取消作業在平行模式程式庫中如何運作的詳細資訊,請參閱 PPL 中的取消

[頂端]

從 ActiveX 移轉

如需如何從 ActiveX 版本的 Bing 地圖服務路線最佳化程式移轉至 Windows 市集應用程式的詳細資訊,請參閱在 Bing 地圖服務路線最佳化程式範例中移轉現有程式碼

[頂端]

後續步驟

如需 C++ Windows 執行階段元件如何與 JavaScript 元件相互操作的詳細資訊,請參閱 Bing 地圖服務路線最佳化程式範例中 JavaScript 和 C++ 之間的交互作業

[頂端]

請參閱

概念

Bing 地圖服務路線最佳化程式範例中 JavaScript 和 C++ 之間的交互作業

在 Bing 地圖服務路線最佳化程式範例中使用 JavaScript

其他資源

開發 Bing 地圖服務路線最佳化程式 (以 JavaScript 和 C++ 撰寫的 Windows 市集應用程式)