Web/DB プログラミング徹底解説

ホーム > 雑記帳 > BackgroundWorker の使い方 [.NET Framework]

BackgroundWorker の使い方 [.NET Framework]

注: このテクノロジは .NET Framework 2.0 以降で利用可能です

イベントハンドラに直接時間のかかる処理を書くと、UI が、いわゆる「固まった」状態になります。これを避けるために、 時間のかかる処理を実行する場合は、UI を制御するスレッドとは別の作業スレッド (Worker Thread) でそれを実行します。 UI を持つプログラムの場合は、通常ユーザーにその作業の進捗状況を表示する必要があるため、UI と、何らかのやり取りが発生します。

BackgroundWorker を利用するには以下を行います:

  1. DoWork イベントハンドラの設定。これは時間のかかる処理はここから呼び出します。
  2. 処理を実行するときは、RunWorkerAsync メソッドを呼び出します
  3. 処理の進捗を UI スレッドで受け取るときには、ProgressChanged イベントを処理します。
    一方作業スレッドでは、ReportProgress メソッドを呼び出します。
  4. 処理の完了は RunWorkerCompleted イベントが発生することでわかります
つまり、BackgroundWorker の RunWorkerAsync メソッドを呼び出すことによって、DoWork イベントハンドラが実行され、 そこで適宜(処理の進捗があり次第) ProgressChanged イベントを発生させ、処理が終わったら RunWorkerCompleted イベントを発生させる。 ProgressChanged イベントと RunWorkerCompleted イベントを呼び出し元フォームで処理することによって、Worker スレッドの 処理進捗状況を UI に反映させることができる、ということになります。

その他、オプショナルの処理としては次のようなものがあります:

  • 処理のキャンセル - UI スレッドでは CancelAsync メソッドを呼び出し BackgroundWorker オブジェクトに処理のキャンセルをリクエストします。作業スレッドでは、時々CancellationPending プロパティをチェックして、処理を中断するかどうか判断します。
  • 作業スレッドにパラメータを渡す場合には、(処理を開始する) RunWorkerAsync メソッドにパラメータを渡します。 渡されたパラメータは DoWork イベントハンドラに渡される DoWorkEventArgs の Argument プロパティに渡されます。

Screen Shot

簡単なテストコードは次のとおり。

サンプルコード (Visual Studio 2005 C# Windows Application Project)

作業スレッドでは、Sleep を使って処理を遅延させています。処理の開始時にはドロップダウンリストからパラメータを取得し、 かつ、キャンセルボタンでその処理を中断可能としています。

namespace backgroundworder_test {

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


   /////////////////////////////////////////////////////////////////////////


   public partial class Form1 : Form {

     public Form1() {
       InitializeComponent();
     }


     /////////////////////////////////////////////////////////////////////


     private void Form1_Load( object sender, EventArgs e ) {

       //
       // イベントハンドラの設定
       //

       backgroundWorker1.DoWork 
         += new DoWorkEventHandler( backgroundWorker1_DoWork );
       backgroundWorker1.ProgressChanged 
         += new ProgressChangedEventHandler( backgroundWorker1_ProgressChanged );
       backgroundWorker1.RunWorkerCompleted 
         += new RunWorkerCompletedEventHandler( backgroundWorker1_RunWorkerCompleted );
     
       //
       // コントロールの初期化
       //

       comboBox1.SelectedIndex = 1;

     }


     /////////////////////////////////////////////////////////////////////


     void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {

       //
       // ボタンの設定
       //

       btnDoWork.Enabled = true;
       btnCancel.Enabled = false;
       
       //
       // エラーまたはキャンセルのチェック
       //

       if (e.Error != null) {
         MessageBox.Show( e.Error.Message );
         return;
       } else if (e.Cancelled) {
         MessageBox.Show( "Operation has been cancelled." );
         return;
       }
       
       //
       // 処理完了
       //

       WORK_RESULT res = e.Result as WORK_RESULT;

       if (res == null) {
         throw new Exception( "Invalid Context" );
       }

       MessageBox.Show(
         string.Format( "Work Completed. {0} - {1}", res.value, res.msg )
       );

     }


     /////////////////////////////////////////////////////////////////////


     void backgroundWorker1_ProgressChanged( object sender, ProgressChangedEventArgs e ) {

       progressBar1.Value = e.ProgressPercentage;
     
     }


     /////////////////////////////////////////////////////////////////////


     private void backgroundWorker1_DoWork( object sender, DoWorkEventArgs args ) {

       // パラメータの取得
       int nMillSec = (int) args.Argument;

       // 戻り値の準備
       WORK_RESULT res = new WORK_RESULT();

       // ワーカーオブジェクトの取得
       BackgroundWorker worker = sender as BackgroundWorker;
       if (worker == null) {
         throw new Exception( "Invalid Context" );
       }

       // 時間のかかる処理・・・
       for (int i = 0; i < 20; i++) {
         // キャンセルのチェック
         if (worker.CancellationPending) {
           args.Cancel = true;
           return;
         }

         // Sleep による遅延
         System.Threading.Thread.Sleep( 1 * nMillSec );
         worker.ReportProgress( (i + 1) * 5 );
       }

       // 結果の設定
       res.value = 20;
       res.msg = "Completed";
       args.Result = res;
     }


     /////////////////////////////////////////////////////////////////////


     private void button1_Click( object sender, EventArgs e ) {

       btnDoWork.Enabled = false;
       btnCancel.Enabled = true;

       int nParam = int.Parse( comboBox1.Text );

       progressBar1.Value = 0;
       backgroundWorker1.RunWorkerAsync( nParam );

     }


     /////////////////////////////////////////////////////////////////////


     private void button2_Click( object sender, EventArgs e ) {

       if (DialogResult.Yes == MessageBox.Show(
         "Are you sure you want to cancel this operation?",
         "Confirmation",
         MessageBoxButtons.YesNo )) {
       
         backgroundWorker1.CancelAsync();
       
       }

     }
   }


   //
   // WORK RESULT
   //

   public class WORK_RESULT {
     public int value;
     public string msg;
   }

}