非同期処理、マルチスレッド(余談:別スレッドからフォームコントロールを操作)
そもそもフォームもクラスですし、クラスにあるオブジェクトを別スレッドから操作は出来ません。フォーム自体がスレッドセーフではないから当然と言えば当然なんですが、非同期処理をやるとカウンタや進捗表示など、どうしてもフォームオブジェクトへアクセスしたくなる場面に出くわします。
ボタン1個、ラベル1個を用意したフォームアプリケーションを用意して下さい。
以下はやったらダメな例です。System.ThreadingをUsingして実行して下さい。
private void button1_Click(object sender, EventArgs e) { Thread testThread = new Thread(new ThreadStart(HeavyProc)); testThread.Start(); } private void HeavyProc() { for (int i = 0; i <= 5; ++i) { Thread.Sleep(1000); } label1.Text = "終わったよ"; //←ここでInvalidOperationExceptionが発生 }
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim testThread As New Thread(New ThreadStart(AddressOf HeavyProc)) testThread.Start() End Sub Private Sub HeavyProc() For i As Integer = 0 To 5 Thread.Sleep(1000) Next Label1.Text = "終わったよ" '←ここでInvalidOperationExceptionが発生 End Sub
メインスレッドでもラベルに対して操作出来る状況で、別スレッドから操作されたら駄目なのでエラーになります。
Invokeメソッドの利用
この問題を解決するにはいくつか方法がありますが、一番メジャーな方法としてはInvokeメソッドを利用することです。元々System.Windows.Forms名前空間には別スレッドからの操作用としてInvokeメソッドが提供されています。
コントロール操作をデリゲートで作成し、そのインスタンスをInvokeメソッドへ渡せば実現可能となります。
//ラベル操作のデリゲート delegate void labelnaiyo(string naiyo); private void SetLabel(string naiyo) { label1.Text = naiyo; } private void button1_Click(object sender, EventArgs e) { Thread testThread = new Thread(new ThreadStart(HeavyProc)); testThread.Start(); } private void HeavyProc() { for (int i = 0; i <= 5; ++i) { Thread.Sleep(1000); } Invoke(new labelnaiyo(SetLabel),"終わったよ"); //←デリゲートのインスタンスを渡す }
’ラベル操作のデリゲート Delegate Sub labelnaiyo(ByVal naiyo As String) Private Sub SetLabel(ByVal naiyo As String) Label1.Text = naiyo End Sub Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim testThread As New Thread(New ThreadStart(AddressOf HeavyProc)) testThread.Start() End Sub Private Sub HeavyProc() For i As Integer = 0 To 5 Thread.Sleep(1000) Next Invoke(New labelnaiyo(AddressOf SetLabel), "終わったよ") '←デリゲートのインスタンスを渡す End Sub
コード上は別スレッドに書かれていますが、デリゲート自体はメインスレッドで実行されますので、特に問題無くラベルに文字が表示されるようになります。
なお、System.Windows.Forms名前空間にはInvokeRequiredプロパティが提供されています。これはInvokeが必要なのかどうかをチェックするプロパティで、これを使ってInvokeメソッドの実装必要性を判断すると良いです。