- PR -

ループ処理で中断・再開するには

投稿者投稿内容
飛行船
会議室デビュー日: 2008/04/11
投稿数: 7
投稿日時: 2008-04-11 01:17
お世話になります。

VB2005を使って、for nextのループを用いたプログラムを作っています。

そのループの中で、各回の処理(フォームの入力項目の自動入力など)の最後で一時中断をして、ボタンをクリックして再開させたいと思います。

中断〜再開の間には、該当フォームの入力項目を手作業で変更します。
その作業が終わったら、再開ボタンを押して、次のループ処理に移ります。


ここで質問です。

当初は、メッセージボックスを使って中断し、ボタンを押したら再開することを考えていましたが、メッセージボックスが表示されている間は、フォームにアクセスできません。

次に考えたのが、処理の中断を一定時間カウントさせるループ処理によりWait状態とし、再開ボタンがクリックするのを検知したら、waitループをexitするというものです。

プログラムの処理はこんな感じです。


Private sub main

for i = 1 to 100
  処理
  call wait
next

End sub


Private sub wait

 Do while 一定時間待つ
  If restart = True Then
   restart = False
  Exit Do
 End If
 Loop

End sub


Prive sub Button1_click
 restart = True
End sub


上記のような処理で、一応、中断〜再開はできるようになったのですが、どうもメインプログラムの動作が不安定です。
具体的には、フォームの入力項目の自動入力処理が、たまにうまくいかないことがあるのです。
中断〜再開処理を削除し、ループ処理だけにすると処理は正常に行われるので、中断〜再開処理に問題があるものと推測しています。

そもそも、ループ処理の中断〜再開を行う方法として、上記のような方法が良いのかも自信がありません。

ループ処理の中断〜再開には、本来どのような処理をすればよいのかご指導いただきたく思います。

よろしくお願いします。
nakaP
大ベテラン
会議室デビュー日: 2005/09/27
投稿数: 138
お住まい・勤務地: 高知
投稿日時: 2008-04-11 09:31
こんにちは。
動作が不安定になるのは、Do〜Loopをまわし続けているからです。
Do〜Loop中はApplication.DoEvents()を入れてもCPU使用率が100%になってしまいます。(タスクマネージャで確認)

精査は必要ですが、以下のものである程度望まれているものができると思います。
コード:
Private pause As Boolean = False
Private idx as Integer = 0

Private Sub StartButton_Click
    pause = False
    For i As Integer = idx To 10000
        If pause Then Exit For
        	
        Me.Label1.Text = i.ToString()
        idx = i
        Application.DoEvents()
    Next i
End Sub

Private Sub StopButton_Click
    pause = True
End Sub


テッテ
ベテラン
会議室デビュー日: 2008/03/16
投稿数: 91
投稿日時: 2008-04-11 10:09
自動入力で中断・再開・・・ちょっとやりたいことが理解できませんでしたので、
外していたらすみません。

バックグラウンドで処理させたいのであれば、マルチスレッドにした方がよいと思います。
VB2005以降なら BackgroundWorker で比較的簡単に実装できます。

参考URL
http://www.atmarkit.co.jp/fdotnet/dotnettips/436bgworker/bgworker.html
飛行船
会議室デビュー日: 2008/04/11
投稿数: 7
投稿日時: 2008-04-11 15:22
nakaPさん、こんにちは

コードをご提示くださってありがとうございます。

おかげさまで、一時停止〜再開のロジックはよくわかりました。

早速テストしたところ、StopButtonをクリックしても、pauseできないことがあります。
何度かクリックすれできるんですが。

そういえば、私が示したコードでも、Button1(再開ボタン)を押しても再開できないことがたまにあったことと同様な現象ではないかと思いました。

waitに入ったとき、ボタンがクリックされたことを確実に判断するためのヒントをお示しいただけないでしょうか。


あと、「Do〜Loop中はApplication.DoEvents()を入れてもCPU使用率が100%になってしまいます」とのことですが、確かにそうなりました。以前どこかでみかけたのですが、それを防止するためには、System.Threading.Thread.Sleepを加えればよいとのことでした。

飛行船
会議室デビュー日: 2008/04/11
投稿数: 7
投稿日時: 2008-04-11 15:33
テッテさん、こんにちは

ご回答ありがとうございます。


>自動入力で中断・再開・・・ちょっとやりたいことが理解できませんでしたので、

フォームの入力項目に自動入力をしていくのですが、自動入力処理の後、念のため、人手によるチェックを行い、必要に応じて修正を加えたいのです。そのため、自動入力処理が終わったら、いったん処理を中断させ、人手によるチェック・修正が完了したら、再開、すなわち、次のfor next処理に移りたい、というものです。


>バックグラウンドで処理させたいのであれば、マルチスレッドにした方がよいと思います。

上記のような中断〜再開処理の場合、どの処理をバックグランドにもっていけばよいのか、よくわかりません。
具体的に教えていただけませんでしょうか。

よろしくお願いします。


テッテ
ベテラン
会議室デビュー日: 2008/03/16
投稿数: 91
投稿日時: 2008-04-11 16:22
よくわかりました。
やはりマルチスレッドにしたほうが安定すると思います。
そうすれば、CPU使用率が上がることも、ボタンがときどき反応しないようなこともなくなります。

この場合は、for ループを丸ごと BackgroundWorker で実行すればよいと思います。
ただし、フォーム上のコントロールへのアクセスは Invoke を使わなければならないので、
少々面倒にはなりますが。

使い方は先ほどのURLを参考にしてください。
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2008-04-11 16:46
引用:

飛行船さんの書き込み (2008-04-11 01:17) より:
そのループの中で、各回の処理(フォームの入力項目の自動入力など)の最後で一時中断をして、ボタンをクリックして再開させたいと思います。



ループ部分を分解することをオススメします。
Win32やWindows.Formsのメッセージング・イベントは
長期間処理をすることを前提に作られていません。

自前のループの中でWindowsのメッセージポンプに処理をもどす、
つまりループでApplication.DoEventsを行うと
さまざまな部分で不具合が生じます。

#顕著なのはフォームを閉じたときでしょうか。

ループを分解し、
ループカウンタを使うなどしてループの状態をフォームに記憶させ、
タイマーやApplication.Idleで処理をするといいでしょう。

このようにするとスレッドを使ったり、細かいところに気を使わなくても、
うまく動きます。

ボタンがうまく押せなかったり、
CPU利用率が100%になってしまったり、
フォーカスがうまく動かなかったり、
メッセージボックスが表示されなかったり、
閉じるボタンをおしたらエラーになったり、
そういった問題を全て避けることができます。

コルーチンを自分で実装しろ、ということですね。
nakaP
大ベテラン
会議室デビュー日: 2005/09/27
投稿数: 138
お住まい・勤務地: 高知
投稿日時: 2008-04-11 18:04
引用:

れいさんの書き込み (2008-04-11 16:46) より:

ループを分解し、
ループカウンタを使うなどしてループの状態をフォームに記憶させ、
タイマーやApplication.Idleで処理をするといいでしょう。

(中略)

コルーチンを自分で実装しろ、ということですね。


コルーチンという言葉を初めて聞いたので、少し惹かれて作ってみました。
コード:
Private Sub StartButton_Click
    StartButton.Enabled = False
    StopButton.Enabled = True
    
    pause = False
    AddHandler Application.Idle, Application_Idle
End Sub

Private Sub EndButton_Click
    pause = True
    StartButton.Enabled = True
    StopButton.Enabled = False
End Sub

Private pause As Boolean = False
Private idx As Integer
Private Sub LoopMethod()
    If pause Then
        RemoveHandler Application.Idle, Application_Idle
        Return
    End If
    CountLabel.Text = idx.ToString()
    idx += 1
End Sub

Private Sub Application_Idle
    LoopMethod()
End Sub


こういうことなのかな?
確かに、Application.DoEvents()を使うよりも安定しているかも。

スキルアップ/キャリアアップ(JOB@IT)