[ VB.NET / C# ] BackgroundWorker を使ったマルチスレッドのサンプル

Pocket

.Net Framework 2.0 以降では、BackgroundWorker コンポーネントを使用してマルチスレッドアプリケーションを作成することができるようになりました。ここでは、BackgroundWorker を使った簡単なサンプルを掲載しています。

スポンサーリンク

サンプルコードの GUI

サンプルコードの GUI になります。開始ボタンでスレッドを起動し、キャンセルボタンスレッドを停止しています。下図から分かるように BackgroundWorker コンポーネントを設置しています。プログレスバーではスレッドの進捗を表示しています。

サンプルコードのGUI

サンプルコードのGUI

サンプルソースのポイントや注意点

サンプルソースを確認するポイントを列挙しています。サンプルのソースコードにもコメントを記載していますので、詳細はそちらも参照ください。

  • スレッドの実行とパラメータの受け渡し型
  • メインスレッドからのスレッドのキャンセル(停止)
  • スレッドからメインスレッドへの進捗(完了)およびその他情報の通知とその受け取り方
  • スレッドから TextBox などのコントロールの直接操作はできない

BackgroundWorker を使ったマルチスレッド

VB.NET と C# でのサンプルソースです。詳細はコメントを参照ください。(長いです・・・)

VB.NET

Imports System.ComponentModel

Public Class Form1

    ''' フォームロードイベント
    Private Sub Form1_Load(ByVal sender As Object, _
                           ByVal e As System.EventArgs) Handles Me.Load

        ' キャンセルボタンは初期状態では無効とする
        CancelButton.Enabled = False

        ' スレッドのキャンセルを可能とする
        BackgroundWorker1.WorkerSupportsCancellation = True

        ' スレッドからの進捗通知を可能とする
        BackgroundWorker1.WorkerReportsProgress = True
    End Sub

    ''' 開始ボタンクリックイベント
    Private Sub StartButton_Click(ByVal sender As System.Object, _
                                  ByVal e As System.EventArgs) Handles StartButton.Click
        '------------------------------------------
        ' スレッドの多重起動はできないための制御
        '------------------------------------------
        ' GUIで制御する場合
        StartButton.Enabled = False ' スタートボタンを無効にする
        CancelButton.Enabled = True ' キャンセルボタンを有効にする

        '' プログラムで制御する場合
        'If BackgroundWorker1.IsBusy Then
        '    MessageBox.Show("すでに実行中です")
        '    Return
        'End If

        '------------------------------------------
        ' スレッドの起動
        '    BackgroundWorker1_DoWork メソッドが別スレッドで実行される
        '    パラメータも渡している(Object型なので、パラメータに制約はない)
        '------------------------------------------
        Dim arg As Integer = 30
        BackgroundWorker1.RunWorkerAsync(arg)

    End Sub

    ''' キャンセルボタンクリックイベント
    Private Sub CancelButton_Click(ByVal sender As System.Object, _
                                   ByVal e As System.EventArgs) Handles CancelButton.Click
        ' GUIで制御する場合
        StartButton.Enabled = True ' スタートボタンを有効にする
        CancelButton.Enabled = False ' キャンセルボタンを無効にする

        '' プログラムで制御する場合
        'If Not BackgroundWorker1.IsBusy Then
        '    MessageBox.Show("スレッドは実行されていません")
        '    Return
        'End If

        '------------------------------------------
        ' スレッドのキャンセル
        '   強制的に停止するのではなく、スレッドにキャンセルを通知する
        '   スレッドキャンセルの決定はスレッドメソッド内で行う。
        '------------------------------------------
        BackgroundWorker1.CancelAsync()

    End Sub

    ''' 別スレッドで動作するメソッド
    Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
                                         ByVal e As System.ComponentModel.DoWorkEventArgs) _
                                         Handles BackgroundWorker1.DoWork

        ' パラメータをObject 型からキャストして取り出す
        Dim seconds As Integer = CType(e.Argument, Integer)

        ' パラメータの秒数分スレッドを実行させる
        For i As Integer = 1 To seconds

            ' キャンセル通知があればスレッドをキャンセルする
            If BackgroundWorker1.CancellationPending Then
                e.Cancel = True
                '---------------------------------
                ' キャンセル時は、e.Resultを設定しても完了通知で受け取れない
                '---------------------------------
                e.Result = "スレッドキャンセル"
                Return
            End If

            '1秒停止
            System.Threading.Thread.Sleep(1000)

            Dim prog As Integer = i * 100 / seconds ' 進捗を計算(単位:%)
            Dim msg As String = "スレッド開始後、" + i.ToString() + " 秒経過しました。"

            '------------------------------------------
            ' 進捗を通知する
            ' ・BackgroundWorker1_ProgressChanged がメインスレッドから呼び出される
            ' ・パラメータ1:進捗(%)       Integer型
            ' ・パラメータ2:その他あれば    Object型
            '------------------------------------------
            BackgroundWorker1.ReportProgress(prog, msg)

        Next

        e.Result = "スレッド完了"

    End Sub

    ''' スレッドの進捗通知を受け取る
    Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, _
                                                  ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
                                                  Handles BackgroundWorker1.ProgressChanged

        ' スレッドから通知された、パラメータを受け取る
        ProgressBar1.Value = e.ProgressPercentage   ' パラメータ1
        TextBox1.Text = CType(e.UserState, String)  ' パラメータ2(Object型なのでキャスト)

    End Sub

    ''' スレッドの完了通知を受け取る
    Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, _
                                                     ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
                                                     Handles BackgroundWorker1.RunWorkerCompleted
        'ボタンの有効無効を設定する
        StartButton.Enabled = True ' 開始ボタン
        CancelButton.Enabled = False ' キャンセルボタン

        If e.Cancelled Then
            TextBox1.Text = "キャンセルされました"
            ProgressBar1.Value = 0
            '' キャンセル時にResultプロパティから値を取得すると例外が発生する
            'TextBox1.Text = e.Result.ToString()
            Return
        End If

        ' スレッドの結果を取得
        TextBox1.Text = e.Result.ToString()

    End Sub
End Class

C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace SampleApplication
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// フォームロードイベント
        private void Form1_Load(object sender, EventArgs e)
        {
            // キャンセルボタンは初期状態では無効とする
            cancelButton.Enabled = false;

            // スレッドのキャンセルを可能とする
            backgroundWorker1.WorkerSupportsCancellation = true;

            // スレッドからの進捗通知を可能とする
            backgroundWorker1.WorkerReportsProgress = true;
        }

        /// 開始ボタンクリック
        private void startButton_Click(object sender, EventArgs e)
        {
            //------------------------------------------
            // スレッドの多重起動はできないための制御
            //------------------------------------------
            // GUIで制御する場合
            startButton.Enabled = false; // スタートボタンを無効にする
            cancelButton.Enabled = true; // キャンセルボタンを有効にする

            //// プログラムで制御する場合
            //if (backgroundWorker1.IsBusy) {
            //    MessageBox.Show("すでに実行中です");
            //    return;
            //}

            //------------------------------------------
            // スレッドの起動
            //    backgroundWorker1_DoWork メソッドが別スレッドで実行される
            //    パラメータも渡している(object型なので、パラメータに制約はない)
            //------------------------------------------
            int arg  = 30;
            backgroundWorker1.RunWorkerAsync(arg);
        }

        /// キャンセルボタンクリック
        private void cancelbutton_Click(object sender, EventArgs e)
        {
            // GUIで制御する場合
            startButton.Enabled = true; // スタートボタンを有効にする
            cancelButton.Enabled = false; // キャンセルボタンを無効にする

            //// プログラムで制御する場合
            //if (!backgroundWorker1.IsBusy) { 
            //    MessageBox.Show("スレッドは実行されていません");
            //    return;
            //}

            //------------------------------------------
            // スレッドのキャンセル
            //   強制的に停止するのではなく、スレッドにキャンセルを通知する
            //   スレッドキャンセルの決定はスレッドメソッド内で行う。
            //------------------------------------------
            backgroundWorker1.CancelAsync();
        }

        /// 別スレッドで動作するメソッド
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            // パラメータを object 型からキャストして取り出す
            int seconds = (int)(e.Argument);

            // パラメータの秒数分スレッドを実行させる
            for (int i = 1; i <= seconds; i++)
            {
                // キャンセル通知があればスレッドをキャンセルする
                if( backgroundWorker1.CancellationPending ) {
                    e.Cancel = true;
                    //---------------------------------
                    // キャンセル時は、e.Resultを設定しても完了通知で受け取れない
                    //---------------------------------
                    e.Result = "スレッドキャンセル";
                    return;
                }

                //1秒停止
                System.Threading.Thread.Sleep(1000);

                int prog  = (int)(i * 100 / seconds); // 進捗を計算(単位:%)
                string msg = "スレッド開始後、" + i.ToString() + " 秒経過しました。";

                //------------------------------------------
                // 進捗を通知する
                // ・backgroundWorker1_ProgressChanged がメインスレッドから呼び出される
                // ・パラメータ1:進捗(%)       int型
                // ・パラメータ2:その他あれば    object型
                //------------------------------------------
                backgroundWorker1.ReportProgress(prog, msg);

            }
            e.Result = "スレッド完了";
        }

        /// スレッドの進捗通知を受け取る(メインスレッドで動作する)
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // スレッドから通知された、パラメータを受け取る
            progressBar1.Value = e.ProgressPercentage;   // パラメータ1
            textBox1.Text = (string)e.UserState;  // パラメータ2(object型なのでキャスト)
        }

        /// スレッドの完了通知を受け取る(メインスレッドで動作する)
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //ボタンの有効無効を設定する
            startButton.Enabled = true; // 開始ボタン
            cancelButton.Enabled = false; // キャンセルボタン

            if (e.Cancelled) {
                textBox1.Text = "キャンセルされました";
                progressBar1.Value = 0;
                //// キャンセル時にResultプロパティから値を取得すると例外が発生する
                //textBox1.Text = e.Result.ToString()
                return;
            }

            // スレッドの結果を取得
            textBox1.Text = e.Result.ToString();
        }
    }
}
参考
スポンサーリンク


Pocket

Leave a Comment

Your email address will not be published. Required fields are marked *