このサイトでは、分析、カスタマイズされたコンテンツ、および広告に Cookie を使用します。このサイトを引き続き閲覧すると、Cookie の使用に同意するものと見なされます。
Hi, Developers,
straightapps.com
投稿 November 16, 2018、更新 April 26, 2019
トップページ > Windows トップ > イベントによる簡易的な処理中止指示

Windows 開発

ワーカースレッド等を使用せず、長い処理の中断を指示する方法について、書いています。

重複ファイルを検出する DupliFileDetector ユーティリティ は、 ワーカースレッド等を使わず、メインのスレッドでファイルを検索し、比較して結果を出力するまで止まりません。 指定フォルダに、そのサブフォルダを含めて検索することができるため、指定の仕方によっては長時間かかり、 指定ミスであったとしても、ループに入っているため止めることができません

もちろん、本来的には UIスレッド ( 一番簡単な方法は、モードレスダイアログでしょう。 ) ワーカースレッド ( 記憶では、CreateThread 関数呼び出しで、スレッドを作成できます。 ) を作成して、キャンセルボタンを装備し、 そこから処理を中断できるようにするべきですが、基本的には自分のみ使う、ちょっとしたツールなので、 なるべく苦労なくしたいものの、しかし中断したいケースも(特に開発中に)あり、検討しました。

ここでは、ハードディスクにある重複ファイルを検出するツールで使用している、 イベントによる簡易的な処理中止指示について書いています。

このページにトピック一覧がありますので、あわせてご参照ください。
ご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。

イベントによる中止とは

投稿 November 16, 2018

イベントは、CEvent クラスで 簡単に実装できます。

長時間の処理に入るプログラム側では、ループ中にイベントを検査し、セットされていたら中断する、という処理を入れておきます。 コードは異なっても、UIスレッド(例えば作成しておいたモードレス・ダイアログ)からの通知でも、構造的には同じでしょう。

重複ファイルを検出する DupliFileDetector ユーティリティ では、 二重起動を(簡易的に)検出し、2つ目であると判断されたら(すでに起動されていると判断されたら)、 中断イベントを発行するようにしています。

つまり、1つ目を起動して重複ファイル検索を開始、長い処理に入ってしまったので中止させたいときは、 もう1つ(2つ目)を起動して中断イベントを発行して、1つ目のループ処理を中止させることができるようになっています。

あくまで実装を簡単にするための方法ですので、通常は、少なくとも中断指示用プログラムを別に用意する、等の配慮が必要です。 ご注意ください。

▲ページ先頭へ

イベントを用意・検出する

投稿 November 16, 2018

イベントとして、CEvent クラスを使います。

CEvent g_evStop(FALSE, TRUE, _T("SA_DFDetector_Cancel"));	// 中止イベント

グローバルに、このように定義しているものとします。
変数名(上では g_evStop)は何でも構いません。第3引数の文字列は、システム内でユニーク(唯一)になるべき識別子ですので、そこそこ複雑にするほうが安全です。

長い処理を開始する前に、イベントがリセットされていることを保証します。 でないと、イベントがセットされている状態から開始した場合、すぐに中止判定となってしまいます。

// 中止イベントをリセットします。
g_evStop.ResetEvent();

ループ前に、イベント検出の準備もしておきます。
ここでは監視すべきイベントは1つだけですが、複数のイベントを待てるので、無駄にも見えますが、 CMultiLock クラスを用意しています。
なお、NUM_EVENTS は事前に定義した、待つ(検査する)イベントの数で、ここでは 1 にしています。

CSyncObject* syncs[NUM_EVENTS];
syncs[0] = &g_evStop;	// [1] 以降も増やせます

CMultiLock lock(syncs, NUM_EVENTS);

あとは、ループ内でイベントがセットされたかを検査し、セットされていれば中止できるようにしておきます。

DWORD dwResult = lock.Lock(1, FALSE);	// イベント待ち
if (WAIT_TIMEOUT != dwResult){		// イベントなくタイムアウトでないとき
	DWORD dwEventID = dwResult - WAIT_OBJECT_0;	// イベントにより分岐

	if (dwEventID == 0){
		g_evStop.ResetEvent();
		// 中止イベント検出 → 中止処理へ
	}
}

Lock 関数で、1msだけ、syncs に登録されているイベントを待っています。 イベントがセットされずに指定の時間(この場合は 1ms)が経過すると、WAIT_TIMEOUT が返されますので、 それ以外が返されたときが、イベントがあったときです。

イベントがあると、dwResult に、WAIT_OBJECT_0 以降の、 syncs に登録されたイベントのインデックスが設定されます。
WAIT_TIMEOUT は 258、WAIT_OBJECT_0 は 0 と定義されていますので、イベントを最大 258 個まで登録でき、 dwResult - WAIT_OBJECT_0 は実質意味がありませんが、このようにしておくほうが(将来的に)安全と言えるようです。

今回は1つしかイベントを登録していませんので、発生したイベントを調べる必要はありませんが、 イベントIDが0であることを確認し、イベントをリセットして、中止処理へ進むようにしています。

▲ページ先頭へ

イベントをセットする

投稿 November 16, 2018

ここまでで、イベントを検査し、中断する処理が実装されましたので、次はそのイベントを発行する処理を実装します。

まず、ここでは同一プログラムにイベント発行機能を入れているのですが、別プログラムでも問題ありません。
イベント名に同じ文字列(ここでは "SA_DFDetector_Cancel")を指定すると、システムで有効となります。 このため、簡単な文字列は避ける必要がある、というわけです。

面倒なので同一プログラム内に発行機能も設けている本プログラム(ダイアログアプリ)では、 CEvent オブジェクトを DupliFileDetectorDlg.cpp で定義しているので、 DupliFileDetector.cpp から見えるように、次の宣言を入れています。

extern CEvent g_evStop;			// 中止イベント

そして、InitInstance 関数でメインダイアログを起動する前に、 次のようなコードでイベントを発行するかどうかを決めています。

HWND hwnd = FindWindow(NULL, "Duplicate File Detector");
if (hwnd){
	if (IDOK == MessageBox(NULL, _T("処理中の場合は、実行停止イベントを発行できます。\n停止させるには、「OK」を選択してください。"), _T("すでに起動中されています"), MB_ICONINFORMATION | MB_OKCANCEL)){
		g_evStop.SetEvent();
		goto StopEventIssued;
	}
}

FindWindow 関数で、指定の文字列のウィンドウを検索しています。 つまり、すでに本プログラムが起動中であるかを確認しています。
この方法は完全ではありません。1回目の起動でダイアログを作成し終わる前に2回目の起動が行われると、 FindWindow はウィンドウを見つけられません。CMutex 等を使用すべきようです。

ウィンドウが見つかったらメッセージを出し、中断イベントを発行するかを問い合わせます。 発行する場合はイベントをセットし、メインダイアログを作成せずに、自身を終了させます。 発行しない場合は処理を続行し、2つ目のインスタンスを作成します。

ラベル StopEventIssued: は、メインダイアログ起動のあとに付けてあります。 基本的には C/C++ で goto 文は悪ですが、 一連のメインダイアログの処理を if 内に入れると見にくくなるので、 利用範囲を制限して goto を容認する考えに賛同しています。

▲ページ先頭へ


関連トピックス

関連トピックはありません。


その他のおすすめ

おすすめ記事はありません。



© 2017-2021 StraightApps.com 無断転載を禁じます。No reproduction without permission.