Bing Maps Trip Optimizer のサンプルでの C++ の使用
このドキュメントでは Bing マップ トリップ オプティマイザーの C++ コンポーネントについて説明します。 C++ コンポーネントは、他の Windows ランタイム コンポーネントとのインターフェイスを提供するために、ルーティングの最適化と Visual C++ コンポーネント拡張 (C++/CX) コードのロジックを実装する純粋なネイティブ コードを使用します。
注意
C++ で記述された Windows ストア アプリで利用できる言語拡張である C++/CX の詳細については、「Visual C++ の言語リファレンス (C++/CX)」を参照してください。
基本的な C++ Windows ランタイム コンポーネントを作成する方法については、「チュートリアル : C++ での基本 Windows ランタイム コンポーネントの作成、および JavaScript または C# からの呼び出し」を参照してください。
アプリの C++ の部分は、Windows ランタイム ダイナミック リンク ライブラリ (DLL) として書き込まれます。 このライブラリは、次の機能を提供します。
Bing マップの Web サービスから各場所の緯度と経度を取得します。 この操作は、Bing マップの Representational State Transfer (REST) インターフェイスを使用します。
旅行中に行くあらゆる 2 地点間の距離を取得します。 この操作にも、Bing マップの REST インターフェイスが使用されます。
ルートの最適化を実行します。 この操作では、アリの巣の最適化アルゴリズムと並行プロセスを使用して、最適なルートを効率的に計算します。
これらの操作は、バックグラウンドで非同期的に行われます。 これにより、ユーザー インターフェイスは最適化プロセス中でもすばやく応答できます。
C++ コンポーネントが使用する主要機能およびテクノロジの一部を次に示します。
Windows::Foundation::IAsyncOperationWithProgress<TResult, TProgress> オブジェクトを作成する concurrency::create_async。 JavaScript アプリケーションはこのオブジェクトを使用して、最適化タスクの進行状況と完了を監視または取り消します。 コンポーネントは、Bing マップとの通信および背景で最適化タスクを実行するために concurrency::task を使用します。
位置とルート情報を取得する Bing マップの REST サービス。
Bing マップの REST サービスから HTTP ストリームを非同期に要求および読み込む IXMLHTTPRequest2。
Bing マップの Web サービスから応答を読み込む Windows::Data::Xml::Dom::XmlDocument。
concurrency::parallel_for_each は、ルーティングの最適化アルゴリズムを並列化するために使用されます。
注意
このドキュメントに対応するサンプル コードは、「Bing Maps trip optimizer sample (Bing マップ トリップ オプティマイザー サンプル)」にあります。
目次
コード規則
ファイルの構成
TripOptimizer クラスと TripOptimizerImpl クラスの作成
構成のワークフロー
フェーズ 1: 位置データの取得
フェーズ 2: ルート データの取得
フェーズ 3: 最適化されたルートの計算
HTTP 機能の定義
最適なルートの計算
キャンセルの処理
ActiveX からの移行
次の手順
コード規則
Windows ストア コンポーネントの作成に Windows ランタイムを使うことに加えて、C++ コンポーネントではスマート ポインターや例外処理などの最新コード規則を使用します。
Windows ランタイムは、信頼できるオペレーティング システムの環境でのみ動作する Windows ストア アプリの作成に使用するプログラミング インターフェイスです。 このようなアプリは承認済みの関数、データ型、およびデバイスを使用し、Windows ストアから配布されます。 Windows ランタイム は、アプリケーション バイナリ インターフェイス (ABI) によって表されます。 ABI は、Windows ランタイム API を Visual C++ などのプログラミング言語で使用できるようにするための基になるバイナリ コントラクトです。
Windows ランタイム を使用するアプリケーションの記述を簡単にするために、Microsoft は特に Windows ランタイム をサポートする Visual C++ の言語拡張機能を提供しています。 これらの言語拡張機能の多くは、C++ または CLI 言語の構文に似ています。 ただし、ネイティブ アプリは共通言語ランタイム (CLR: Common Language Runtime) をターゲットとするのではなく、この構文を使用して Windows ランタイムをターゲットとします。 ハット (^) 修飾子は、参照カウントによってランタイム オブジェクトの自動削除を有効にするため、この新しい構文の重要な部分です。 オブジェクトがスコープの外に出るか、すべての参照を nullptr に設定するなど、オブジェクトが他のコンポーネントから参照されていない場合、Windows ランタイム オブジェクトの有効期間を管理する AddRef や Release などのメソッドを呼び出す代わりに、ランタイムはオブジェクトを削除します。 Windows ストア アプリを作成するための Visual C++ のもう 1 つの重要な部分は ref new キーワードです。 参照カウントを使用する Windows ランタイム オブジェクトを作成するときは、new ではなく ref new を使用します。 詳細については、「Windows Runtime objects and ref new (Windows のランタイム オブジェクトと ref new)」を参照してください。
重要
^ および ref new を使用する必要があるのは、Windows ランタイム オブジェクトの作成時または Windows ランタイム コンポーネントの作成時のみです。Windows ランタイム を使用しないコア アプリケーション コードを記述する場合は C++ の標準構文を使用できます。
注意
Bing マップ トリップ オプティマイザーは ^ を Microsoft::WRL::ComPtr、std::shared_ptr、および std::unique_ptr と共に使用して、ヒープに割り当てられたオブジェクトを管理し、メモリ リークを最小限にします。Windows ランタイム 変数の有効期間を管理するには ^、COM 変数の有効期間を管理するには Microsoft::WRL::ComPtr、ヒープ上に割り当てられた他のすべての C++ オブジェクトの有効期間を管理するには shared_ptr または unique_ptr を使用することをお勧めします。
C++ の最新のプログラミングの詳細については、「C++ へようこそ (Modern C++)」を参照してください。 C++ Windows ストア アプリで利用できる言語拡張機能の詳細については、「Visual C++ の言語リファレンス (C++/CX)」を参照してください。
[このページのトップへ]
ファイルの構成
次のリストでは、C++ コンポーネントの一部である各ソース コード ファイルの役割について手短に説明しています。
AntSystem.h, AntSystem.cpp
アリの巣の最適化アルゴリズムとその関連データ構造体を定義します。HttpRequest.h, HttpRequest.cpp
非同期 HTTP 要求を実行するためのヘルパー クラスである HttpRequest クラスを定義します。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 (Modern 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 ランタイム コンポーネント クラスを作成するとき、public キーワードと sealed キーワードを使用してください。Windows ストア アプリから呼び出すことができる Windows ランタイム コンポーネントを C++ で作成する方法の詳細については、「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 ランタイム コンポーネントは、利用できるようになった戻り値を取得するためにこのオブジェクトを使用できます。 このインターフェイスは、呼び出し元も有効にして、操作の進行状況を監視し、発生したエラーを受信できます。
ヒント
推奨される慣行として、非同期メソッドの名前は Async で終わらせ、メソッドは Windows::Foundation::IAsyncAction、Windows::Foundation::IAsyncActionWithProgress<TProgress>、Windows::Foundation::IAsyncOperation<TResult>、またはWindows::Foundation::IAsyncOperationWithProgress<TResult, TProgress> を返すようにします。
Windows ランタイム が有効な各言語 (C++、JavaScript など) は、非同期操作を作成する独自の方法を規定します。 C++ では、concurrency::create_async 関数を使用できます。 この関数は IAsyncAction、IAsyncActionWithProgress<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 Maps Trip Optimizer のサンプルでの 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 を呼び出すときに発生する操作の流れを示しています。
非同期操作の一部として 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 メソッドは、次の 3 つのフェーズでトリップの最適化を実行します。 第 1 フェーズでは、このメソッドは Bing マップから場所データを取得します。 第 2 フェーズでは、このメソッドは、トリップで可能なすべてのポイント間のルート情報を取得します。 第 3 フェーズでは、このメソッドは、トリップの最適化アルゴリズムを実行します。 それぞれのフェーズは、前のフェーズのタスクの継続として機能します。 継続処理により、タスクが完了した後でも 1 つ以上のタスクを実行できるようになります。 継続は、このドキュメントの後で詳しく説明します。 ただし、継続はバックグラウンドで動作するため、タスクを後でアクセスできるように、タスク間で共有される変数を格納する必要があることに注意してください。 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 クラスの直接的なメンバーとして作成することもできました。しかし、パラメーター構造体を提供することで、トリップ最適化操作の状態をそのアクションと明確に関連付けることができました。メンバー変数を使用した場合、複数のトリップ最適化を同時に実行するなどの機能をサポートすることは困難です。
フェーズ 1: 位置データの取得
第 1 フェーズでは、TripOptimizerImpl::InternalOptimizeTripAsync メソッドは Bing マップから場所データを取得します。 位置データは、Bing マップの入力が正しいことをユーザーが確認できるように取得されます。 たとえば、「ピッツバーグ市」を指定した場合、Bing マップでは「ペンシルベニア州ピッツバーグ市」なのか、「オンタリオ州ピッツバーグ市」なのか、「ジョージア州ピッツバーグ市」なのか判別できません。 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(¶ms->RequestsPending);
}));
}
return tasks;
}
TripOptimizerImpl::RetrieveLocationsAsync メソッドは、concurrency::task::then メソッドも使用して、Bing マップからの応答を、受信するたびに処理します。 task::then メソッドは継続タスクを作成します。これは前のタスク、つまり継続元のタスクが完了すると実行されるタスクです。 TripOptimizerImpl::RetrieveLocationsAsync メソッドの最後に行われる when_all への呼び出しは、すべてのタスクおよび継続タスクが完了するまで待機しています。 C++ のタスクおよび継続の詳細については、「タスクの並列化」を参照してください。
Bing マップ REST API は、XML データを返します。 TripOptimizerImpl::ProcessLocation メソッドは、指定された XML データ ストリームからの場所の場所情報を読み込みます。 このメソッドは、指定された XmlDocument オブジェクトを処理するために、XmlDocument::SelectSingleNodeNS を使用します。 この例は、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 ストリームからのリソースの合計数を読み取ります。 リソースは、場所名と一致する候補を示します。 たとえば、「ピッツバーグ市」を指定した場合、Bing マップでは「ペンシルベニア州ピッツバーグ市」、「オンタリオ州ピッツバーグ市」、および「ジョージア州ピッツバーグ市」を返す場合があります。 次に、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 メソッドは第 2 フェーズに移動し、Bing マップからルート データを取得します。
Bing マップのロケーション サービスの詳細については、「Locations API (ロケーション API)」を参照してください。
フェーズ 2: ルート データの取得
第 2 フェーズでは、TripOptimizerImpl::InternalOptimizeTripAsync メソッドは Bing マップからルート データを取得します。 ルート データは、トリップ最適化アルゴリズムで、グラフの各ポイント間の距離を必要とするため取得されます。 先に説明したように、グラフの 2 つのノードを接続するのはエッジで、各ノードは場所を保持します。
//
// 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(¶ms->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 マップのルート サービスの詳細については、「Routes API (ルート API)」を参照してください。
フェーズ 3: 最適化されたルートの計算
第 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 は Windows::Foundation::Collections::IMap<K, V> インターフェイスの C++ 実装です。 同様に、Platform::Collections::Vector は Windows::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 サービスに接続するための 1 つの方法です。アプリでサービスに接続するためのその他のオプションの詳細については、「Web サービスへの接続 (C#/VB/C++ と XAML を使った Windows ストア アプリ)」を参照してください。
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::DownloadAsync を呼び出します。 HttpRequest::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);
});
}
最適化されたルートを計算するために、発生した XML データを TripOptimizerImpl クラスが使用する方法については、このドキュメントの「構成のワークフロー」を参照してください。 IXMLHTTPRequest2 の他の使用方法の例については、「Quickstart: Connecting using XML HTTP Request」および「チュートリアル: タスクおよび XML HTTP 要求を使用した接続」を参照してください。
[このページのトップへ]
最適なルートの計算
ルートの計算を実行するコア アルゴリズムは AntSystem.h と AntSystem.cpp で定義されます。 ルートの計算は巡回セールスマン問題に似ています。 巡回セールスマン問題では、場所の集合および場所と場所の距離が与えられた場合、それぞれの場所を 1 回だけ訪問する最短ルートを計算することが目標です。 巡回セールスマン問題は、利用可能なすべてのルートを計算する必要があるため、最適なルートを探すのはこれまで困難でした。 アリの巣最適化アルゴリズムは、このクラスの問題に対するメタヒューリスティックな解決方法です。 これは、最適化されたルートを探すためにアリの動作をシミュレートします。 このアルゴリズムによって生成されるルートは与えられた場所の集合体で必ずしも最短になるとは限りませんが、多くの場合は最短となるか、トラベルに適した短い距離となります。
AntSystem.h と AntSystem.cpp ファイルは AntSystem 名前空間を定義します。 この名前空間には Windows ランタイム の依存関係がなく、C++/CX を使用しません。 AntSystem.h は、LatLong、Node、および Edge の構造体を定義します。 また、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 構造体は 2 つのノードを接続し、その間の距離を保持します。 アリの巣の最適化アルゴリズムによって使用されるデータも保持します。
// 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 を参照してください。
ただし、アルゴリズムの実装の重要な側面として同時実行の使用があります。 アリの巣の最適化アルゴリズムは次の 3 つの基本的な手順を複数のイテレーションによって実行します。それぞれのアリがグラフを行き巡れるようにする、フェロモンを蒸発させる、それぞれのアリが足跡をたどって開始点まで戻れるようにする。 最初のステップ、つまりそれぞれのアリがグラフを行き巡れるようにするでは、それぞれのアリが独立して動くため並列して実行できます。 この手順では、共有データまたは依存の計算は含まれません。
// 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 のオプションとして提供されています。
[このページのトップへ]
キャンセルの処理
IAsyncAction、IAsyncActionWithProgress<TProgress>、IAsyncOperation<TResult>、および IAsyncOperationWithProgress<TResult, TProgress> インターフェイスはそれぞれ、非同期操作を取り消すことができるように Cancel メソッドを提供しています。 C++ では、Cancel メソッドは、非同期操作に関連付けられたキャンセル トークンを取り消します。 Windows ランタイム Cancel メソッドを使用してタスクのキャンセルをつなげる方法は 2 つあります。 最初に、create_async に渡す処理関数が concurrency::cancellation_token オブジェクトとなるように定義できます。 Cancel メソッドが呼び出されると、このキャンセル トークンは取り消され、正常なキャンセルの規則が適用されます。 cancellation_token オブジェクトを指定しない場合、基になる task オブジェクトが暗黙的に定義します。 処理関数のキャンセルに協調的に応答する必要がある場合は cancellation_token オブジェクトを定義します。 このキャンセル機構の詳細については、「C++ における Windows ストア アプリ用の非同期操作の作成」を参照してください。
キャンセルは、ユーザーが JavaScript アプリの Cancel ボタンをクリックするか、または回復不能なエラーが生じた場合に発生します。 ユーザーによるキャンセルを有効にすることは、最適化タスクで UI を応答可能な状態に保つことが重要な 1 つの理由です。 キャンセルは即座には発生しません。 C++ コンポーネントは concurrency::cancellation_token_source および concurrency::cancellation_token を使用して、キャンセルの信号を送る、またはキャンセルをときどき確認します。 C++ コンポーネントは、粒度の粗いレベルの大半でキャンセルを実装しますが、キャンセルがタイムリーに発生するようにしています。 パフォーマンスの観点から、アプリケーションが頻繁にキャンセルを確認しても有効ではありません。 実際、キャンセルのチェックに費やされる時間が、実際の作業時間を上回るならパフォーマンスの低下となります。
C++ のコンポーネントは、2 つの方法でキャンセルをチェックします。 最初に、最適化の各フェーズ後に生じる継続タスクが concurrency::task::get を呼び出して、キャンセルをテストします。 task::get メソッドは、キャンセルが発生した場合、task_canceled も含めてタスク中に発生した任意の例外をスローします (when_all の場合、複数のタスクがスローされるとランタイムで例外の 1 つが選択されます)。発生したすべてのタスク例外を確認する必要があるため、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 メソッドを使用してエラーをテストします。 後で例外の 1 つを再スローする予定なので、空の 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.
}
});
});
}
コンポーネントがキャンセルをチェックする 2 番目の方法は、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;
}
注意
ここで、cancellation_token::is_canceled を呼び出す代わりに is_task_cancellation_requested と cancel_current_task を呼び出すという最初の手法を使用することもできました。ただし、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 からの移行
Bing マップ トリップ オプティマイザーの ActiveX バージョンから Windows ストア アプリへの移行に関する詳細については、「Bing Maps Trip Optimizer のサンプルでの既存のコードの移行」を参照してください。
[このページのトップへ]
次の手順
C++ Windows ランタイム コンポーネントが JavaScript コンポーネントと相互運用する方法については、「Bing Maps Trip Optimizer のサンプルでの JavaScript と C++ 間の相互運用」を参照してください。
[このページのトップへ]
参照
概念
Bing Maps Trip Optimizer のサンプルでの JavaScript と C++ 間の相互運用
Bing Maps Trip Optimizer のサンプルでの JavaScript の使用
その他の技術情報
JavaScript および C++ での Windows ストア アプリ (Bing Maps Trip Optimizer) の開発