在 Bing 地图行程优化器示例中使用 C++

本文档介绍 Bing 地图行程优化器的 C++ 组件。 C++ 组件使用纯本机代码实现路线优化的逻辑和 Visual C++ 组件扩展 (C++/CX) 代码,以便与其他 Windows 运行时组件交互。

备注

有关 C++/CX(对用 C++ 编写的 Windows 应用商店应用程序可用的语言扩展)的详细信息,请参见 Visual C++ 语言参考 (C++/CX)

有关如何创建基本 C++ Windows 运行时组件的信息,请参见演练:用 C++ 创建一个基本的 Windows 运行时组件,然后从 JavaScript 或 C# 中调用该组件

应用程序的 C++ 部分以 Windows 运行时动态链接库 (DLL) 的方式进行编写。 此库提供以下功能:

  • 通过 Bing 地图 Web 服务检索每个位置的经纬度。 此操作使用 Bing 地图具象状态传输 (REST) 接口。

  • 检索行程中每对可能的点之间的行程距离。 此操作还使用 Bing 地图 REST 接口。

  • 执行路线优化。 此操作使用蚁群优化算法和并行处理来有效计算优化路线。

这些操作以异步方式执行或在后台执行。 这使得用户界面可在优化过程中保持响应状态。

C++ 组件使用的一些主要功能和技术如下:

备注

对应于本文档的代码示例可在 Bing 地图行程优化器示例中找到。

本文档的内容

  • 代码约定

  • 文件组织

  • 创建 TripOptimizer 和 TripOptimizerImpl 类

  • 组件工作流

    • 阶段 1:检索位置数据

    • 阶段 2:检索路线数据

    • 阶段 3:计算优化路线

  • 定义 HTTP 功能

  • 计算最佳路线

  • 处理取消

  • 从 ActiveX 中迁移

  • 后续步骤

代码约定

除了使用 Windows 运行时创建 Windows 应用商店组件外,C++ 组件还使用现代代码约定,如智能指针和异常处理。

Windows 运行时是一个编程接口,可使用它创建仅在可信操作系统环境下运行的 Windows 应用商店应用程序。 此类应用程序使用授权的功能、数据类型和设备,并从 Windows 应用商店分发。 Windows 运行时由应用程序二进制接口 (ABI) 表示。 ABI 是一种基础二进制协定,该协定允许 Visual C++ 等编程语言使用 Windows 运行时 API。

为了便于编写使用 Windows 运行时的应用程序,Microsoft 提供了专门支持 Windows 运行时的 Visual C++ 的语言扩展。 其中许多语言扩展类似于 C++/CLI 语言的语法。 但是,本机应用程序使用此语法来面向 Windows 运行时,而不是面向公共语言运行时 (CLR)。 乘幂号 (^) 修饰符是此新语法的重要部分,因为它通过引用计数启用了运行时对象的自动删除。 运行时不是调用方法(例如 AddRefRelease)来管理 Windows 运行时对象的生存期,而是在其他组件不引用对象时(例如,在它离开范围或你将所有引用设置为 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>。 如果此方法要返回 voidcreate_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 helper 函数,该函数返回用提供的结果立即完成的 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-cn,VS.120).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 方法是整个后台任务的一部分,因此该异步操作任务链不会阻止主应用程序。

本文档的定义 HTTP 功能部分中说明了有关 HttpRequest 类的工作方式的详细信息。

// 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 方法创建延续任务,该任务在上一个任务或前面的任务完成后运行。 在 TripOptimizerImpl::RetrieveLocationsAsync 方法结尾对 when_all 的调用会等待所有任务及其延续任务完成。 有关 C++ 中的任务和延续的详细信息,请参见任务并行

Bing 地图 REST API 会返回 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 流中读取资源总数。 资源是指位置名称的可能匹配。 例如,如果指定“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 地图位置服务的更多信息,请参见位置 API

Hh699891.collapse_all(zh-cn,VS.120).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 地图路线服务的更多信息,请参见路线 API

Hh699891.collapse_all(zh-cn,VS.120).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 对象中。(MapWindows::Foundation::Collections::IMap<K, V> 接口的 C++ 实现。 同样,Platform::Collections::VectorWindows::Foundation::Collections::IVector<T> 接口的 C++ 实现。)主应用程序使用位置数据将地图和位置名称显示为转弯路线图的一部分。

//
// 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 是一种连接到 Web 服务的方法。若要了解应用程序中用于连接到服务的其他选项,请参见连接到 Web 服务

由于 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 对象。 然后,它创建一个 task 对象,该对象在 HttpRequestStringCallback 对象的任务完成事件完成时完成。 在任务完成事件完成时,此 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演练:使用任务和 XML HTTP 请求进行连接

[顶部]

计算最佳路线

执行路线计算的核心算法在 AntSystem.h 和 AntSystem.cpp 中定义。 路线计算类似于旅游推销商问题。 旅游推销商问题的目标是根据各种位置以及各位置之间的距离,计算出一次访问完每个位置的最短路线。 由于需要计算每个可能路线来查找最佳路线,因此旅游推销商问题通常很难解决。 蚁群优化算法是用于解决此类问题的启发式方法。 它对蚂蚁的行为建模,以便快速查找优化路线。 虽然此算法生成的路线不一定是给定的一组位置的最短路线,但通常情况下它可以找到最短路线,或者就旅游来说,它可以找到足够短的路线。

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 地图 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 方法可取消与异步操作关联的取消标记。 可通过两种方式将任务取消与 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。但是,必须从 task 对象中调用 cancel_current_task。由于理论上你可以使用此实现从 task 或代码的另一部分中调用 AntSystem::OptimizeRoute 函数,因此我们直接使用取消标记以实现较大的灵活性。如果从不使用任务的代码中调用此函数,则可为 cancellationToken 参数传递 concurrency::cancellation_token::nonenone 标记永远无法取消。

定义 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 中迁移

有关如何从 Bing 地图行程优化器的 ActiveX 版本迁移到 Windows 应用商店应用程序的信息,请参见在 Bing 地图行程优化器示例中迁移现有代码

[顶部]

后续步骤

有关 C++ Windows 运行时组件如何与 JavaScript 组件互操作的信息,请参见在 Bing 地图行程优化器示例中的 JavaScript 和 C++ 之间进行互操作

[顶部]

请参见

概念

在 Bing 地图行程优化器示例中的 JavaScript 和 C++ 之间进行互操作

在 Bing 地图行程优化器示例中使用 JavaScript

其他资源

在 JavaScript 和 C++ 中开发 Windows 应用商店应用程序 Bing 地图行程优化器