遅延処理の実装方法について
クリップボード監視における遅延処理は正義だと確信したけれど、複数回発生する無駄な処理をどう捌くかについて考えてみる。
以下のパターンを想定する。
(1) WM_CLIPBOARDUPDATEが発生、SequenceNumber(*1)が更新
(2) WM_CLIPBOARDUPDATEが発生、SequenceNumberが更新、クリップボード内容は変更なし(*2)
(3) (2)と同じ
(4) (2)と同じ (90msの間にWM_CLIPBOARDUPDATEが合計4回発生とする)
ちなみに、Excelや.NETのClipboardクラスではWM_CLIPBOARDUPDATEが2回発生する。また、ExcelのセルドラッグではWM_CLIPBOARDUPDATEが4回発生するが、1~2/3~4回目でクリップボードの内容が違う可能性が高い。(4回目の後にクリップボードが空になるので、カット&ペースト&クリアしている可能性が高い)
(*1) GetClipboardSequenceNumberで返される値
(*2) 少なくともテキスト(CF_TEXT)での値は同一
■ 対応案A
WM_CLIPBOARDUPDATEから100ms後に処理を開始し、クリップボードの内容を履歴に追加する。特に工夫なく(2)~(4)まで同様に処理する。同じ内容を複数回コピーしたのと同じであるため問題はないが、効率は悪くなる。(クリップボードを参照する回数は4回、SequenceNumberは利用しない)
■ 対応案B
WM_CLIPBOARDUPDATEから100ms後に処理を開始し、クリップボードの内容を履歴に追加する。また、この時のSequenceNumberを記録しておく。この場合、(1)に対応する処理では(4)で更新された内容が履歴に入る。(2)~(3)ではSequenceNumberを比較する事により同じ内容である事が判断できるので、クリップボードを開く事なく処理を完了する。(クリップボードを参照する回数は1回、SequenceNumberを取得する回数は4回)
■ 対応案C
WM_CLIPBOARDUPDATE発生直後にSequenceNumberを取得する。100ms後に再度SequenceNumberを確認し、変わっていない場合はクリップボードの内容を履歴に追加する。この場合、(1)~(3)では100ms後にSequenceNumberが更新されているため処理されず、(4)で初めてクリップボードが参照される。(クリップボードを参照する回数は1回、SequenceNumberを取得する回数は8回)
※ どの場合も100ms後というのはメッセージループを止める実装であってはならない。
このうちAはクリップボードを複数回参照しており、クリップボードの参照はSequenceNumberの取得より重く、バッティングの要因であるので採用する意味はない。
BとCを比較した場合、Bの方が効率が良さそうに見えるが、(1)の処理で(4)の結果を取得している点が美しくない。Cでは(1)~(2)の結果は処理する前に上書きされたので無視し、最後に残った(4)だけ処理するという正しさがある。
駄目だ、美しさだけではなく、他の利点も捻り出さねば・・・
以下のパターンを想定する。
(1) WM_CLIPBOARDUPDATEが発生、SequenceNumber(*1)が更新
(2) WM_CLIPBOARDUPDATEが発生、SequenceNumberが更新、クリップボード内容は変更なし(*2)
(3) (2)と同じ
(4) (2)と同じ (90msの間にWM_CLIPBOARDUPDATEが合計4回発生とする)
ちなみに、Excelや.NETのClipboardクラスではWM_CLIPBOARDUPDATEが2回発生する。また、ExcelのセルドラッグではWM_CLIPBOARDUPDATEが4回発生するが、1~2/3~4回目でクリップボードの内容が違う可能性が高い。(4回目の後にクリップボードが空になるので、カット&ペースト&クリアしている可能性が高い)
(*1) GetClipboardSequenceNumberで返される値
(*2) 少なくともテキスト(CF_TEXT)での値は同一
■ 対応案A
WM_CLIPBOARDUPDATEから100ms後に処理を開始し、クリップボードの内容を履歴に追加する。特に工夫なく(2)~(4)まで同様に処理する。同じ内容を複数回コピーしたのと同じであるため問題はないが、効率は悪くなる。(クリップボードを参照する回数は4回、SequenceNumberは利用しない)
■ 対応案B
WM_CLIPBOARDUPDATEから100ms後に処理を開始し、クリップボードの内容を履歴に追加する。また、この時のSequenceNumberを記録しておく。この場合、(1)に対応する処理では(4)で更新された内容が履歴に入る。(2)~(3)ではSequenceNumberを比較する事により同じ内容である事が判断できるので、クリップボードを開く事なく処理を完了する。(クリップボードを参照する回数は1回、SequenceNumberを取得する回数は4回)
■ 対応案C
WM_CLIPBOARDUPDATE発生直後にSequenceNumberを取得する。100ms後に再度SequenceNumberを確認し、変わっていない場合はクリップボードの内容を履歴に追加する。この場合、(1)~(3)では100ms後にSequenceNumberが更新されているため処理されず、(4)で初めてクリップボードが参照される。(クリップボードを参照する回数は1回、SequenceNumberを取得する回数は8回)
※ どの場合も100ms後というのはメッセージループを止める実装であってはならない。
このうちAはクリップボードを複数回参照しており、クリップボードの参照はSequenceNumberの取得より重く、バッティングの要因であるので採用する意味はない。
BとCを比較した場合、Bの方が効率が良さそうに見えるが、(1)の処理で(4)の結果を取得している点が美しくない。Cでは(1)~(2)の結果は処理する前に上書きされたので無視し、最後に残った(4)だけ処理するという正しさがある。
駄目だ、美しさだけではなく、他の利点も捻り出さねば・・・
この記事へのコメント