6)计算最佳路线。执行线路计算的核心算法定义在AntSystem.h和AntSystem.cpp中,这些文件定义了AntSystem命名空间,这个命名空间不包含WinRT上的依附元素,所以并不使用C++/CX。AntSystem.h定义了LatLong,Node和Edge结构体,同时也定义了OptimizeRoute函数。

LatLong结构体表示地图上一个点的经纬度,代码如下。

struct LatLong

{

explicit LatLong(double latitude, double longitude)

: Latitude(latitude)

, Longitude(longitude)

{

}

// 位置坐标

double Latitude;

double Longitude;

};

Node结构体表示图像中一个节点,包含位置的名称,经纬度,也含从Bing Maps服务中而来的替换名称,代码如下。

struct Node

{

explicit Node(const std::wstring& inputName)

: InputName(inputName)

, ResolvedLocation(0.0, 0.0)

{

}

// 用户提供的位置名称

std::wstring InputName;

// Bing Maps位置服务提供的位置经纬度

LatLong ResolvedLocation;

std::wstring ResolvedName;

//

//匹配当前输入所有可能位置的平行数组(数组中包含名称字符串,经纬度等)

//比如在输入一个城市名称后,显示一系列和此城市名相近的(匹配的)具体位置名称。

// 位置数组会包含对应的每个位置经纬度

std::vector<std::wstring> Names;

std::vector<LatLong> Locations;

};

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)

{

}

// 开始节点

std::shared_ptr<Node> PointA;

// 结束节点

std::shared_ptr<Node> PointB;

//棱边的信息数

double Pheromone;

// 始末节点的距离

double TravelDistance;

};

C++组件为每个线路中的位置创建一个Node对象,为每对位置间创建一个Edge对象。在组件集合了所有从Bing Maps web服务上获取的必要信息后,它就会调用OptimizeRoute计算最佳路线,代码如下。

// 计算所给出节点间距离,算出最短距离

// 这个方法提供Node集合

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

算法使用的一个重要方面是利用并发性,蚁群优化算法执行3个基本步骤的几次迭代,代码如下。

// 执行几次蚁群算法模拟

auto startTime = GetTickCount64();

for (unsigned int i = 0; i < iterations; ++i)

{

// 不定期检查是否取消

auto time = GetTickCount64();

if (time - startTime > 100) {

if (cancellationToken.is_canceled()) {

// 返回空集合

return vector<size_t>();

}

startTime = time;

}

// 发送进度

if (progressCallback != nullptr) {

(*progressCallback)(i);

}

// 注意这个操作可并行执行

// 这个步骤不包含共享数据或依附性计算

if (parallel) {

parallel_for_each(begin(ants), end(ants), [&](Ant& blitz) {

blitz.Explore();

});

}

else {

for_each(begin(ants), end(ants), [&](Ant& blitz) {

blitz.Explore();

});

}

//

for_each(begin(edges), end(edges), [rho](shared_ptr<Edge> edge) {

edge->Pheromone *= (1.0 - rho);

});

//图像中回溯

// 注意这个操作不是并行执行的,因为每个棱边的值的更新。

// 这里回溯操作相对来说不是很长

for_each(begin(ants), end(ants), [&](Ant& blitz) {

blitz.Backtrack();

});

}

7)处理取消操作。IAsyncAction,IAsyncActionWithProgress<TProgress>,IAsyncOperation<TResult>,和IAsyncOperationWithProgress<TResult, TProgress>中的每一个接口都会提供一个取消异步操作的“Cancel”方法。可以将任务的取消和WinRT的“Cancel”方法以两种方式关联,首先可以定义传递给create_async的功能函数得到一个Concurrency::cancellation_token对象,当“Cancel”方法被调用时,取消标记(cancellation token)会被撤销。如果没有cancellation_token对象,下层的任务对象会隐式地定义一个,当需要协同响应在功能函数中的取消操作,就定义一个cancellation_token对象。

取消操作是用户在JavaScript应用中选择取消按钮,或一个不可恢复错误发生时出现的,保持界面在优化任务中响应,是为了让用户能够使用取消,而取消不会立刻发生。C++组件使用Concurrency::cancellation_token_source和Concurrency::cancellation_token标志取消并随机地检测取消操作。从一个执行性能的角度讲,如果应用检测取消的操作过于频繁,或者说检查取消的时间多于应用执行正常工作的时间,对应用的性能会产生影响。C++组件以几种方式检测取消,第一种方式是在每个优化阶段,调用Concurrency::is_task_cancellation_requested函数以确定当前任务是否接受到取消执行的请求之后,出现Continuation任务,如果请求了取消,Continuation调用Concurrency::cancel_current_task 将当前任务置于已撤销状态,下面的代码在从Bing Maps上获取路径之前,获取位置之后的时候检测取消操作。

//

// 阶段二: 为每对位置获取路由信息

//

// 报告进度

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

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

// 在所有当前任务完成后前往下一阶段

return when_all(begin(tasks), end(tasks))

.then([=](task<void>) -> task<IMap<String^, IVector<String^>^>^> {

//如果有取消则返回

if (is_task_cancellation_requested()) {

cancel_current_task();

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

}

// 进度报告

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

// 记录HTTP消耗时间

params->HttpTime = GetTickCount64() - params->HttpTime;

第二种方式是在组件收到其他任务的结果时,通过捕获Concurrency::task_canceled检测取消操作。当每个HTTP请求结束时,会在XML数据处理前检查结果代码,下面的代码展示了TripOptimizerImpl::RetrieveLocations方法怎样执行这样的检查,如果取消操作,Concurrency::task::get方法会抛出task_canceled,这样Continuation会返回并不再处理文档内容。

// 下载完成后创建Continuation任务填写位置信息

tasks.push_back(downloadTask.then([=](task<tuple<HRESULT, wstring>> response) {

try {

// 如果下载操作失败,抛出COMException异常

HRESULT hr = get<0>(response.get());

if (FAILED(hr)) {

throw ref new COMException(hr);

}

}

// 操作取消了就不再处理文档

catch (task_canceled&) {

return;

}

try {

// 创建并加载来自响应中的XML文档

XmlDocument^ xmlDocument = ref new XmlDocument();

xmlDocument->LoadXml(ref new String(get<1>(response.get()).c_str()));

// 填写位置信息

ProcessLocation(node, xmlDocument, params->UnresolvedLocations);

}

catch (Exception^) {

// 错误发生,取消任何活跃的操作

m_cancellationTokenSource.cancel();

// 重新抛出异常

throw;

}

// 进度报告

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

}, cancellationToken));

TripOptimizerImpl::RetrieveRoutes方法在每个HTTP请求结束之后,执行了一个相似的检查。接着上面说第三种组件检测取消的方式,是通过调用Concurrency::cancellation_token::is_canceled方法,路线优化算法(即对应的AntSystem::OptimizeRoute函数)以100ms时间为间隔检测取消,代码如下。

// 执行几次模拟

auto startTime = GetTickCount64();

for (unsigned int i = 0; i < iterations; ++i)

{

// 随机检测取消

auto time = GetTickCount64();

if (time - startTime > 100) {

if (cancellationToken.is_canceled()) {

// 返回空集合

return vector<size_t>();

}

startTime = time;

}

与以上类似,HttpRequestCallback类使用Concurrency::cancellation_token::register_callback方法注册一个在取消标记被撤销时调用的回调函数,也可以支持操作取消。这个方法很有用,因为IXMLHTTPRequest2接口执行我们控制之外的异步工作,当取消标记被撤销时,回调函数终止HTTP请求并设置任务结束事件,代码如下。

HttpRequestCallback(IXMLHTTPRequest2 *request, cancellation_token cancellationToken)

: m_dwRef(1)

, m_request(request)

, m_cancellationToken(cancellationToken)

{

// 注册当取消标记被撤销时,中止HTTP操作的回调函数

m_aborted = false;

m_registrationToken = m_cancellationToken.register_callback([this]() {

// 设置中止标志

m_aborted = true;

if (m_request != nullptr) {

m_request->Abort();

}

m_completionEvent.set(make_tuple<HRESULT, wstring>(S_OK, wstring()));

});

}

cancellation_token::register_callback返回一个Concurrency::cancellation_token_registration对象来识别回调注册。HttpRequest类的析构函数使用这个注册对象,来取消回调函数的注册。最好在不再需要回调函数时取消对其的注册,这样保证在回调函数被调用时所有的对象都是有效的,代码如下。

~HttpRequestCallback()

{

// Unregister the callback.

m_cancellationToken.deregister_callback(m_registrationToken);

}

遇到不可恢复错误时,任何遗留的任务都会被取消,例如如果无法处理一个XML文档,全部的操作会被取消并抛出异常,代码如下。

try {

// Create and load the XML document from the response.

XmlDocument^ xmlDocument = ref new XmlDocument();

xmlDocument->LoadXml(ref new String(get<1>(response.get()).c_str()));

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

}

posted on 2013-01-13 07:50  冯瑞涛  阅读(322)  评论(0编辑  收藏  举报