#『応答なし』と表示される原因
処理開始から終了まで一定時間描画系のWindowsメッセージ(WM_PAINT等)が処理されないと『応答なし』が表示されます。
※レジストリの設定にもよりますが、現在主要Windows7以降の標準では5秒が標準となります。
主にシングルスレッドで重い処理(メソッド内で時間のかかる処理をしたりDBから大量のデータを取得等々)した時におこります。ループ処理で調子よく動いているように見えてもフォームをクリックとかしたら途端に『応答なし』になったりしますよね。
#『応答なし』の回避策あれこれ
##Application.DoEventsをコール
まず思いつくのが「DoEvents」。
便利な反面メッセージキューにある全てのWindowsメッセージが処理されるため、思いがけない動作をする可能性があります。
業務用アプリでは二度押し防止策等で動作中各種ボタンを非活性にする等することが多いですが、やはりすべての副作用的要素が取り除けない為、出来れば最終手段としたいものです。
##Win32APIによるWindowsメッセージ処理
次にWin32APIの使用で、描画系のメッセージのみ処理する方法があります。
が、結果的にDoEvents同様各ボタンの非活性化などが必要になります。
(DoEventsより副作用は抑えられるはずですが・・・)
##BackgroundWorkerにる別スレッド実行
ここからが本題ですね。
BackgroundWorkerを使用することで手軽にマルチスレッド処理が実現できます。
注意点としてはワーカースレッドからコントロール等スレッドセーフでないものに直接アクセスしないことでしょうか。処理の中でコントロールへアクセスする必要がある場合、Invokeを用いるか、BackgroundWorkerのProgressChanged・RunWorkerCompletedイベントのようなメインスレッドで実行される部分でする必要があります。
以下簡単なサンプルになります。
・Form1にボタン・プログレスバー・ラベルを一個ずつ配置
Imports System.ComponentModel
Public Class Form1
Private MAX_COUNT = 100
Private WithEvents bgWorker As New BackgroundWorker
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
ProgressBar1.Hide()
Label1.Hide()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ProgressBar1.Maximum = MAX_COUNT
ProgressBar1.Value = 0
ProgressBar1.Step = 1
Label1.Text = String.Empty
'*** 進行状況の報告を有効に
bgWorker.WorkerReportsProgress = True
'*** 処理実施
ProgressBar1.Show()
Label1.Show()
bgWorker.RunWorkerAsync()
End Sub
''' <summary>
''' BackgroundWorkerの処理部
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
Private Sub bgWorker_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgWorker.DoWork
For i = 1 To MAX_COUNT
'*** 重い処理
Threading.Thread.Sleep(500)
'*** プログレス処理
bgWorker.ReportProgress(i)
Next
End Sub
''' <summary>
''' プログレス処理
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
Private Sub bgWorker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bgWorker.ProgressChanged
ProgressBar1.PerformStep()
Label1.Text = String.Format("({0}/{1})", ProgressBar1.Value, MAX_COUNT)
End Sub
''' <summary>
''' BackgroundWorker処理完了時
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
Private Sub bgWorker_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles bgWorker.RunWorkerCompleted
ProgressBar1.Hide()
Label1.Hide()
End Sub
End Class
BackgroundWorker等別スレッドで実行する際もDoEvents同様各ボタンの非活性等で同時実行を制御する必要があることに気を付けてください。
次のステップとして、Task(Async/Await)がありますが、長くなっているので次回に(。-`ω-)b