🖨️

『応答なし』にならないアプリ開発 Part.2

2022/12/11に公開

既存処理の『応答なし』対策救世主?

前回はDoEvents・Win32API・BackgroundWorkerの説明を上げましたが、本題に入る前に今回紹介するTaskについて簡単に紹介したいと思います。
TaskはBackgroundWorkerと違い、既存の処理に少し手を加えるだけで実装可能なので、すでに出来上がっているけど『応答なし』や実行中に固まってしまう事を解決したい・・・というときに活用できます。

まずは比較用に

まずは『応答なし』や実行中はフォームが固まってしまうプログラムです。

  • Form1にボタン・プログレスバー・ラベルを一個ずつ配置
C#サンプルソース
Form1.cs
using System;
using System.Runtime.CompilerServices;
using System.ComponentModel;

public class Form1
{
    private MAX_COUNT = 100;

    private void Form1_Load(object sender, EventArgs e)
    {
        ProgressBar1.Hide();
        Label1.Hide();
    }

    private void Button1_Click(object sender, EventArgs e)
    {
        ProgressBar1.Maximum = MAX_COUNT;
        ProgressBar1.Value = 0;
        ProgressBar1.Step = 1;
        Label1.Text = string.Empty;

        // 処理実施
        ProgressBar1.Show();
        Label1.Show();
        for (var i = 1; i <= MAX_COUNT; i++)
	{
            WaitProcess();

            ProgressBar1.PerformStep();
            Label1.Text = $"({ProgressBar1.Value}/{MAX_COUNT})";
        }
        ProgressBar1.Hide();
        Label1.Hide();
    }

    private void WaitProcess()
    {
        // 重い処理
        Threading.Thread.Sleep(500)
    }
}
VB.Netサンプルソース
Form1.vb
Imports System.ComponentModel
Public Class Form1
    Private MAX_COUNT = 100

    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

        '*** 処理実施
        ProgressBar1.Show()
        Label1.Show()
        For i = 1 To MAX_COUNT
            WaitProcess()

            ProgressBar1.PerformStep()
            Label1.Text = String.Format("({0}/{1})", ProgressBar1.Value, MAX_COUNT)
        Next
        ProgressBar1.Hide()
        Label1.Hide()
    End Sub

    Private Sub WaitProcess()
        '*** 重い処理
        Threading.Thread.Sleep(500)
    End Sub
End Class

#Task(Async/Await)化
上記プログラムをTaskを使用した形に書き換えます。

C#サンプルソース
Form1.cs
using System;
using System.Runtime.CompilerServices;
using System.ComponentModel;

public class Form1
{
    private MAX_COUNT = 100;

    private void Form1_Load(object sender, EventArgs e)
    {
        ProgressBar1.Hide();
        Label1.Hide();
    }

    private async void Button1_Click(object sender, EventArgs e)
    {
        ProgressBar1.Maximum = MAX_COUNT;
        ProgressBar1.Value = 0;
        ProgressBar1.Step = 1;
        Label1.Text = string.Empty;

        // 処理実施
        ProgressBar1.Show();
        Label1.Show();
        for (var i = 1; i <= MAX_COUNT; i++)
	{
            await Task.Run(() => WaitProcess());

            ProgressBar1.PerformStep();
            Label1.Text = $"({ProgressBar1.Value}/{MAX_COUNT})";
        }
        ProgressBar1.Hide();
        Label1.Hide();
    }

    private void WaitProcess()
    {
        // 重い処理
        Threading.Thread.Sleep(500):
    }
}
VB.Netサンプルソース
Form1.vb
Imports System.ComponentModel
Public Class Form1
    Private MAX_COUNT = 100

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        ProgressBar1.Hide()
        Label1.Hide()
    End Sub

    Private Async 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

        '*** 処理実施
        ProgressBar1.Show()
        Label1.Show()
        For i = 1 To MAX_COUNT
            Await Task.Run(Sub()
                               WaitProcess()
                           End Sub)

            ProgressBar1.PerformStep()
            Label1.Text = String.Format("({0}/{1})", ProgressBar1.Value, MAX_COUNT)
        Next
        ProgressBar1.Hide()
        Label1.Hide()
    End Sub

    Private Sub WaitProcess()
        '*** 重い処理
        Threading.Thread.Sleep(500)
    End Sub
End Class

修正点は以下の二点になります。

C#の変更点
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
       ↓
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        for (var i = 1; i <= MAX_COUNT; i++)
	{
            Task.Run(() => WaitProcess());
       ↓
        for (var i = 1; i <= MAX_COUNT; i++)
	{
            await Task.Run(() => WaitProcess());
  • taskを使用するメソッドに『async』を追加
  • 重い処理を『await Task.Run(() => 重い処理)』でくくる
VB.Netの変更点
private void Button1_Click(object sender, EventArgs e)
       ↓
private async void Button1_Click(object sender, EventArgs e)
        For i = 1 To MAX_COUNT
            WaitProcess()
       ↓
        For i = 1 To MAX_COUNT
            Await Task.Run(Sub()
                               WaitProcess()
                           End Sub)
  • Taskを使用するメソッドに『Async』を追加
  • 重い処理を『Await Task.Run(Sub() 重い処理 End Sub)』でくくる

上記の書き換えのみで処理実行中の動作が変わったことが実感できます。

#Task利用時の注意点
このように手軽に利用できそうなTaskですが、先のBackgroundWorker同様にマルチスレッドの処理になりますので、Task処理内でコントロールにアクセスする等そのままでは出来ません。
また、BackgroundWorker等と同様、各ボタンの非活性等で同時実行を制御する必要があります。

Taskについてはまた別にまとめていきたいと思います。

Discussion