🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

Win32 API

Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

Q&A

解決済

3回答

14450閲覧

DoEventsとSleep関数の仕組みをしっかり理解したい

koyamashinji

総合スコア45

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

Win32 API

Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

1グッド

0クリップ

投稿2020/12/26 09:25

編集2020/12/28 14:55

以下、(1)社内サイトからダウンロードしたExcelファイルを開く、(2)開いたエクセルファイルを取得するコードです(動作はOK)。

(★)のDoEventsSleep 100の部分について、

  • DoEventsのみだと、「ブックは取得できませんでした」となる
  • Sleep 100を入れると、正常動作する

のですが、この挙動の違いを**DoEventsSleep関数の仕組みの違いから理解**をしたく。

以下いろいろ調べたところの、私の推測です。
補足等、詳しくご説明頂ける方がいれば是非よろしくお願いいたします。

私の推測

ブックを開く処理と、本コードの実行は、同じプロセス(Excel)であるが、スレッドが異なる。
ブックを開く処理を実施するためには、制御をVBA→OSに移す必要がある。

DoEventsで、制御をVBA→OSに移すが、ココによると、DoEvents0.02秒しか、制御を移せない。
したがって、追加で直後に、Sleep 100を入れることで、0.1秒Sleepさせる。

Sleep関数は、「処理を停止」する関数なので、この関数を入れることで、別スレッド(ブックを開く)が実行できる?
(どこを読んでも、「処理を停止させる」程度の説明なのでここの理解がぼんやりしています)

コード

VBA

123(省略) 456'(1)社内サイトからダウンロードしたExcelファイルを開く 7Dim o As IUIAutomation 8Dim e As IUIAutomationElement 9Set o = New CUIAutomation 10Dim h As LongPtr 11h = ie.hwnd 12h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString) 13If h = 0 Then Exit Sub 14 15Set e = o.ElementFromHandle(ByVal h) 16Dim iCnd As IUIAutomationCondition 17Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "ファイルを開く") 18 19Dim Button As IUIAutomationElement 20Dim InvokePattern As IUIAutomationInvokePattern 21 22While InvokePattern Is Nothing 23DoEvents 24Set Button = e.FindFirst(TreeScope_Subtree, iCnd) 25Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId) 26Wend 27InvokePattern.Invoke '『開く』ボタンを押すコード 28 29 30'(2)自動で開かれたワークブックオブジェクトを取得する 31Dim wb As Workbook 32Dim isFound As Boolean 33isFound = False 34 35Dim i As Long 36For i = 1 To 10000 37 For Each wb In Workbooks 38 If wb.Name = "ダウンロードして開いたブック名.csv" Then 39 Set new_wb = Workbooks("ダウンロードして開いたブック名.csv") 40 isFound = True 41 Exit For 42 End If 43 Next 44 If isFound Then 45 Exit For 46 Else 47 DoEvents (★) 48 Sleep 100 49 End If 50Next 51 52If Not isFound Then 53 MsgBox "開いたブックを取得できませんでした。" 54 Exit Sub 55End If 56 57Debug.Print wb.Name & "取得できました"
broccoli-m0r1👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

挙動の違いをDoEventsとSleep関数の仕組みの違いから理解をしたく。

DoEvents非同期処理の完了の催促、Sleepはその結果をどの程度待つかです。
矢継ぎ早に催促だけして、結果を確認しているという状況ですね。

根本的な理解として必要な事は、同期/非同期です。

(1)社内サイトから自動でExcelファイルを開き

この処理が非同期であるから、待ち合わせの為の処理が必要になるわけで、同期処理として実行できれば、待ち合わせは不要になります。

どのような処理を行っているのか不明なので、役立つか分かりませんが以下参考に。
VBAで他のアプリケーションを同期起動する(WshShell)

DoEvents 関数

DoEvents はオペレーティング システムに制御を渡します。 オペレーティング システムがキュー内のイベントの処理を終了し、SendKeys キューのすべてのキーが送信された後、制御が戻されます。

上記は、自身の処理への割り当てを返却し、他の処理を優先させると読み替える事が出来ます。
ただ、「SendKeys キューのすべてのキーが送信された」状態は、「他の処理の全てが完了した」と同義ではありません。

投稿2020/12/27 02:28

編集2020/12/28 15:54
sazi

総合スコア25327

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

koyamashinji

2020/12/28 03:55

sazi様、ご回答誠にありがとうございます。 また、参考リンクも頂き有難うございます。 「(1)社内サイトから自動でExcelファイルを開き」 この部分をもう少し具体的に説明すると、 データベースからCSV形式のデータをダウンロードし(3-4秒程度かかる)、 IEのダウンロード通知バーの「開く」を押してからExcelブックを開く処理です。 > DoEventsで非同期処理の完了の催促、Sleepはその結果をどの程度待つかです。 以上の文脈の中では、 DoEventsにて、0.02秒x 10,000回ループ=最大200秒間、非同期処理の(1)のスレッドを実行できるので、 普通、200秒間以内に(1)の処理は完了する為、sleepせずとも開けるのではないかと思ってしまいます。 同期、非同期については、知識不足で色々と調べていますが、 いまいち、「催促」と「待つ」の違いに理解が及ばず。 もしよろしければ詳しく教えて頂けないでしょうか。
sazi

2020/12/28 15:57 編集

実際の動作から、DoEventsはキューイングされているわけではないと推測しています。 単にその瞬間に制御がOSに渡るだけ。 そう考えると、sleepなしに実行しても意味が無い事になっている結果と辻褄が合います。 sleepを挟むことで、OSがDoEventsを拾って処理する機会を増やしているという事です。
YAmaGNZ

2020/12/28 04:20

質問文とコメントを見て思ったのですが、DoEventsで0.2秒待機するような勘違いをされているように読めます。 DoEventsでかかる時間は特に決まっていません。 なので、「ある一定時間待つ」という意味合いでDoEventsを使うのは間違っています。
koyamashinji

2020/12/28 11:23

YAmaGNZ様、 ご指摘有難うございます。 >DoEventsでかかる時間は特に決まっていません。 DoEventsでかかる時間というのは、何に依存しているのでしょうか。
YAmaGNZ

2020/12/28 11:40

DoEventsはそのUIスレッドで未処理のイベントメッセージを処理する為の命令と考えた方がいいかと思います。 なので、処理すべきイベントの数やその内容、マシンスペックによって時間は変わります。
guest

0

ベストアンサー

DoEventsとSleepの違いですが、単純に言うと

DoEventsは自分の溜まったイベントを処理する為のもの
Sleepは指定時間の間、自分の処理を止めて、他のスレッドやアプリケーションにCPUを空け渡す

といった感じになります。

参考にされたページのものをループを増やして実行してみれば分かるのですが、DoEventsの処理の方はUIを操作でき、Sleepのほうは操作ができないかと思います。

投稿2020/12/28 11:47

編集2020/12/28 11:51
YAmaGNZ

総合スコア10469

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

koyamashinji

2020/12/28 14:58

ご説明頂き有難うございます。 ・今回のケースにおける、「イベント」の理解が曖昧です。 「ブックを開く」イベントは、「『開く』ボタンを押すコード(※質問文に追記しました)」が実行された時点で発生済だと思いますが、その他の操作を一切しない場合、1回目のループでDoEventsに辿り着いた時点で「溜まったイベント」とは何を指すのでしょうか。
YAmaGNZ

2020/12/28 23:57

ここでいうイベントメッセージとは主にOSがアプリケーションに対して「マウスが動いた」とか「表示しろ」などを通知するためのメッセージを指します。 例えば、MouseMoveのイベントの処理を行っていなかったとしても、OSからは「マウスが動いた」という事象はイベントメッセージとして通知されます。 このOSから通知されるメッセージは、VBAの場合は内部で処理され意識することはほぼありませんが、自分が作成したコードと同一スレッドで動作する為、自分で作成したソースが動いている間はイベントメッセージが処理できなくなってしまいます。 なので、自分の処理が途中の時にイベントメッセージを一旦処理してもらおうというのがDoEventsになります。 たとえば、ループで1000件のデータを処理して、その進行度を画面に表示しようとした場合 For i = 1 To 1000 '処理 Label1.Caption = i & "件" Next とすると、Label1のテキストは処理が終わった後に更新されます。 これは、Label1のテキストを変更した時に「再描画しろ」とイベントメッセージが来ているのですが、こちらはループの処理を行っているのでメッセージの処理ができません。 この為、ループが終わってイベントメッセージが処理できるようになってから「再描画しろ」というイベントメッセージが処理され実際に再描画されます。 このループ内にDoEventsを入れると、そのたびにイベントメッセージを処理するようになるので「再描画しろ」というイベントメッセージは1回のループごとに処理されます。 また、DoEventsを呼んだときに処理すべきイベントメッセージが何もなければ処理しないだけです。
koyamashinji

2020/12/29 05:26

ご丁寧に大変分かりやすい説明有難うございます。 おかげ様で、DoEventsの仕組みがだんだんと理解できてきました。 (まだ頭の中を整理中ですが・・・。) MouseMoveのイベント処理に係る時間はほんの「一瞬」(マウスポインタを別の場所に描画するだけ)であるのに対し、 ブックを開くイベント処理には「『開く』ボタンを押す~ブックが完全に開く」まで比較的長時間(5秒程度)かかります。 後者のイベント処理には時間を要する為(重い処理である)、CPUを空け渡してあげることが必要であることから、DoEventsの直後にSleep関数が必要、との理解で間違ってないでしょうか。 逆に言えば、MouseMoveのイベント処理のみであれば、sleep関数は不要?
YAmaGNZ

2020/12/29 07:17

MouseMoveの処理だけのことを書きますが 1.マウスカーソルを動かす 2.OSがマウスカーソルが座標Aに動かされたということを検知し、マウスカーソルの位置を更新する 3.OSは動作しているアプリケーションに対してマウスカーソルが動かされたということをメッセージで通知する 4.アプリケーションが自分のメッセージを受け取る処理で3のマウスカーソルが動かされたというメッセージを受け取りキューに保存する 5.受け取ったキューの中身を確認し、もしMouseMoveイベントを発生させる必要があるならMouseMoveイベントを発生させる 6.MouseMoveイベントハンドラの処理が行われる 7.受信したメッセージがなくなるまで5~6の処理を行う といった流れが大まかな流れとなります。 もし、ループなどで処理を行っていたら上記でいう5~が動作しなくなります。 この時、OSからのマウスカーソルが動かされたというメッセージはキューに保存されて、忘れてしまうわけではありません。 そして時間がかかる処理が終わったりした時に、そのキューに保存されていたメッセージが処理され5~の処理が行われます。 DoEventsはDoEventsを呼んだタイミングで5~の処理を実行します。これが私の説明で「自分の溜まったイベントを処理する」と言っている部分になります。 なので、DoEventsにかかる時間は5~7にあるように、受信したメッセージの量、処理すべき内容、イベントハンドラ内で実行させるべき処理によって変化することになります。 MouseMoveイベントの処理にどれくらいかかるかはMouseMoveイベントのイベントハンドラに書いた処理次第です。 ただマウスカーソルの座標を変数に入れるという処理であれば一瞬でしょうが、もしこのMouseMoveのイベントハンドラに10秒かかる処理があった場合はDoEventsに10秒かかることになりますし、マウスカーソルの座標をメッセージボックスで表示するという処理にした場合、メッセージボックスを閉じるまでの時間がかかります。 DoEventsはあくまで自分自身の処理をどうするかというものです。 (実行したときに他のスレッド、アプリケーションなどにCPUを明け渡すかもしれませんが、これは主目的ではなく副作用であって必ずこうなるという期待して使用するべきではありません) また、Sleepは先の説明にあるように「指定時間自分の処理を止めて、他のスレッドやアプリケーションにCPUを空け渡す」というものです。 ループでセットで使用するのは 1.他で処理を行う 2.1の結果や待った時間の間に起こった自分自身のイベントを処理する というのを繰り返すということで、自分自身のループ内部の処理に専念するのではなく、他の処理にもCPUを使わせるし、自分宛のメッセージも処理しながらループ内部の処理を行うということです。
koyamashinji

2020/12/29 14:31

たびたびご丁寧な回答深謝致します。 今回の、ブックを開く際の状況に当てはめると、 ブックを開くコードが実行された時点では、4.の状態であって、正確にはイベントは発生しておらず、アプリケーション(Excel)のキューに保存されたのみである。1回目のDoEventsに到達した際に、初めて「開く」というイベントを発生させる(5.の部分)、との理解をしました。 >MouseMoveのイベントハンドラに10秒かかる処理があった場合はDoEventsに10秒かかることになりま>す 今回の「ブックを開く」という処理には先述のとおり5秒程度かかりますので、「ブックを開く」というイベントハンドラに5秒かかる。と解釈すると、ループ1回目のDoEventsで、5秒かけてブックを開くのを完了してくれるのでは、と思ってしまいます。 大変、理解が悪く誠に申し訳ございません。今回のブックを開く、という例に基づき、イベント処理の機序をご説明頂けないでしょうか。ご無理を申し上げて恐縮です。
YAmaGNZ

2020/12/29 15:03

>今回の「ブックを開く」という処理には先述のとおり5秒程度かかりますので、「ブックを開く」というイベントハンドラに5秒かかる。と解釈すると、ループ1回目のDoEventsで、5秒かけてブックを開くのを完了してくれるのでは、と思ってしまいます。 再三言っていますが、DoEventsは時間を待つ時に使うものではありません。 5秒待つのであればSleepを使ってください。 そして、待つときにSleep(5000)と単純に5秒待つとUIが反応しなくなります。 なので For i=1 To 50 Sleep(100) DoEvents Next といった感じでSleepを短時間にしてDoEventsを挟むことによって自分のイベントを処理できるようにします。
koyamashinji

2020/12/30 01:26

ご丁寧に教えていただきまして、大変感謝しています。今回、おかげ様でかなり理解が深まりました。これからも勉強させて頂きます。
sazi

2020/12/30 03:41

@koyamashinzjiさん どのような場合にイベントが発生し、どのような順番で行われるかを理解した上で、イベント処理を実装されている場合を除き、DoEventsを使用する場合は、他にイベント処理を行っていないケースでの使用をお薦めします。 また、回答したとおりDoEventsを採用するより、同期処理を行うようにした方が、イベントの沼に嵌るのを回避できますので。
koyamashinji

2020/12/30 09:07

sazi様、 追加のご助言を頂きまして、誠に有難うございます。同期/非同期処理の概念を全く存じ上げておりませんでしたので、大変勉強になりました。同期処理での実装にもトライしてみようと思います。また、よろしくお願いいたします。(ベストアンサーにできず申し明けございません。下名にとっては、両者ともベストアンサーです)
sazi

2020/12/30 14:12

BAについては気になさらずに。イベントの沼経験者としては同じように嵌って欲しくなかったのでコメントしたのです。
koyamashinji

2020/12/31 00:54

sazi様、大変有難うございます。もっと勉強致します。
guest

0

Workbooksオブジェクトの内容が明らかにされていないし、ループを1万回繰り返してわざとPCに負荷をかけている理由も全く分からないのですが、
単純に、Sleepがない場合、「If wb.Name = "ダウンロードして開いたブック名.csv"」がTrueになるにFor文の1万回リピートが終わってしまい、
isFound が True にならないので
正常動作していないように見えるだけなのではないのでしょうか?

投稿2020/12/26 09:44

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問