佐々木屋

技術的なことから趣味まで色々書きます

非同期処理でフォームOpen/Closeを制御する

非同期処理中はスレッドセーフでなければアクセス出来ないことは以前説明しました。
sasaki816.hatenablog.com
今回は同じような内容ですが、非同期でフォームを開いたり閉じたりする処理を考えます。例えば、ボタンクリックを非同期処理で行い、その別スレッドの処理中に新しいフォームを開き、5秒後に閉じるような処理です。


まず同期処理の場合は以下となります。

private void button1_Click(object sender, EventArgs e) {
    SubForm frm = new SubForm();
    frm.Show();
    System.Threading.Thread.Sleep(5000);
    frm.Close();
}
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim frm As SubForm = New SubForm()
    frm.Show()
    System.Threading.Thread.Sleep(5000)
    frm.Close()
End Sub

このような単純な処理です。特に問題ありませんね。

.NET Framework4.0以前の場合

非同期処理はThreadでやって出来ないことはないですが、Task.Factory.StartNewを使用するのがベストでしょう。フォームオープン処理を別スレッドで実行できるようにメソッドを外だしします。

private void button1_Click(object sender, EventArgs e) {
    Task t = Task.Factory.StartNew(FormOpen);
}

private void FormOpen() {
    SubForm frm = new SubForm();
    frm.Show();
    System.Threading.Thread.Sleep(5000);
    frm.Close();
}
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim t As Task = Task.Factory.StartNew(AddressOf FormOpen)
End Sub

Private Sub FormOpen()
    Dim frm As SubForm = New SubForm()
    frm.Show()
    System.Threading.Thread.Sleep(5000)
    frm.Close()
End Sub

一見全く問題ないように見えますが、これを実行すると新しく開いたフォーム(SubForm)が固まったようになります。

これはフォームがスレッドセーフではない為に発生する現象です。ボタンクリック以下のFormOpenメソッドは別スレッドで実行されていますが、フォームを開く行為は同期処理となります。よって次の処理であるSystem.Threading.Thread.Sleep(5000)が実行されると、スレッド自体はその処理を待ってしまうのです。

これを回避するためには、以前説明したラベルの表示を非同期で切り替える方法と同様でInvokeメソッドに頼ることになります。InvokeメソッドからOpne/Closeメソッドを実行することで、別スレッドでフォームの描画を実行させる形です。

delegate void dlgt();
private void FormOpen() {
    SubForm frm = new SubForm();
    this.Invoke(new dlgt(frm.Show));
    System.Threading.Thread.Sleep(5000);
    this.Invoke(new dlgt(frm.Close));
}
Private Delegate Sub dlgt()
Private Sub FormOpen()
    Dim frm As SubForm = New SubForm()
    Me.Invoke(New dlgt(AddressOf frm.Show))
    System.Threading.Thread.Sleep(5000)
    Me.Invoke(New dlgt(AddressOf frm.Close))
End Sub


.NET Framework4.5以上の場合

async/awaitが使用できるので、Invokeメソッドに頼ることなくより安全に且つ簡単に別スレッドからUIへアクセスできます。
ボタンクリックイベント自体をasyncにしているので、フォームOpen/Close処理をawaitの外にだしてあげます。

private async void button2_ClickAsync(object sender, EventArgs e) {
    SubForm frm = new SubForm();
    frm.Show();
    await Task.Run(() => System.Threading.Thread.Sleep(5000));
    frm.Close();
}
Private Async Sub Button2_ClickAsync(sender As Object, e As EventArgs) Handles Button2.Click
    Dim frm As SubForm = New SubForm()
    frm.Show()
    Await Task.Run(Sub()
                        System.Threading.Thread.Sleep(5000)
                   End Sub)
    frm.Close()
End Sub



なお、フォーム操作を別スレッドでするのは動作上好ましくない(危険な)場合が多いので、非同期処理を深く理解していない状態で安易に非同期で実行することはあまりおすすめしません。