マルチスレッドプログラミング(4):マルチスレッドとUI操作


ユーザーの操作に迅速に対応できるように、Windowsアプリケーションを開発する際にスレッドを使用することがよくあります.時間のかかる操作では,スレッドを使用しないとUIインタフェースが長時間停滞し,ユーザが非常に見たくない状況であり,この問題を解決するためにスレッドを使用することを望んでいる.マルチスレッド操作インタフェースUIを使用するコードを次に示します.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadPoolDemo
{
    public partial class ThreadForm : Form
    {
        public ThreadForm()
        {
            InitializeComponent();
        }

        private void btnThread_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(new ThreadStart(Run));
            thread.Start();
        }

        private void Run()
        {
            while (progressBar.Value < progressBar.Maximum)
            {
                progressBar.PerformStep();
            }
        }
    }
}

プログラムのインタフェースは以下の通りです.「起動」ボタンをクリックしてシミュレーション操作を開始し、進捗バーに操作の全体的な進捗を表示することを目的としています.しかし、本当に「起動」ボタンをクリックすると、Systemが投げ出されるのでがっかりします.InvalidOperationException例外.例外の説明は、「スレッド間の操作が無効です.コントロール『progressBar』を作成したスレッドからアクセスします.」CheckForIllegalCrossThreadCalls属性がこのような状況になったのは.NETでは、デバッグ環境でスレッドを使用して自分で作成したUIコントロールにアクセスすることは許可されていません.これは、マルチスレッド環境でインタフェースコントロールを操作すると予知できない場合を恐れている可能性があります.開発者が自分のコード操作の境界面に問題がないことを確認できれば、比較的簡単な方法で解決することができます.CheckForIllegalCrossThreadCallsという静的プロパティを設定します.デフォルトはtrueです.falseに設定すれば、マルチスレッド環境でも操作インタフェースに異常はありません.上のコードは次のように変更できます.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadPoolDemo
{
    public partial class ThreadForm : Form
    {
        public ThreadForm()
        {
            InitializeComponent();
        }

        private void btnThread_Click(object sender, EventArgs e)
        {
            //            ,        UI         
            CheckForIllegalCrossThreadCalls = false;
            Thread thread = new Thread(new ThreadStart(Run));
            thread.Start();
        }

        private void Run()
        {
            while (progressBar.Value < progressBar.Maximum)
            {
                progressBar.PerformStep();
            }
        }
    }
}

これでプログラムを実行しても異常は出ません.
しかし、上記のコードを使用すると、スレッド内でインタフェースを直接操作することは許されないので、Invokeメソッドを使用することもできます.
Invokeメソッドは、インタフェースを操作するための次の例です.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadPoolDemo
{
    public partial class ThreadForm : Form
    {
        //  delegate  Invoke   
        private delegate void SetProgressBarValue(int value);
        public ThreadForm()
        {
            InitializeComponent();
        }

        private void btnThread_Click(object sender, EventArgs e)
        {
            progressBar.Value = 0;
            //            ,        UI         
            //CheckForIllegalCrossThreadCalls = false;
            Thread thread = new Thread(new ThreadStart(Run));
            thread.Start();
        }
        //            
        private void Run()
        {
            while (progressBar.Value < progressBar.Maximum)
            {
                progressBar.PerformStep();
            }
        }

        private void btnInvoke_Click(object sender, EventArgs e)
        {
            progressBar.Value = 0;
            Thread thread = new Thread(new ThreadStart(RunWithInvoke));
            thread.Start();
        }
        //  Invoke        
        private void RunWithInvoke()
        {
            int value = progressBar.Value;
            while (value< progressBar.Maximum)
            {
                //        
                if (InvokeRequired)
                {
                    this.Invoke(new SetProgressBarValue(SetProgressValue), value++);
                }
                else
                {
                    progressBar.Value = ++value;
                }
            }
        }
        // SetProgressBarValue        
        private void SetProgressValue(int value)
        {
            progressBar.Value = value;
        }
    }
}

このメソッドの機能は上記の操作と同じですが、CheckForIllegalCrossThreadCallsプロパティを設定する必要はありません.また、例外は投げ出されません.もちろん、上記の方法のほかに、BackgroundWorkerクラスを使用して同じ機能を完了することもできます.
BackgroundWorkerクラスの操作インタフェースBackgroundWorkerクラスの操作UIインタフェースを使用する例は周公ブログにすでに例があるので、ここでの例コード注釈は比較的簡単で、読者は周公の以前の例を見ることができ、今回使用したコード例は以下の通りである.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadPoolDemo
{
    public partial class ThreadForm : Form
    {
        //  delegate  Invoke   
        private delegate void SetProgressBarValue(int value);
        private BackgroundWorker worker;
        public ThreadForm()
        {
            InitializeComponent();
        }

        private void btnThread_Click(object sender, EventArgs e)
        {
            progressBar.Value = 0;
            //            ,        UI         
            //CheckForIllegalCrossThreadCalls = false;
            Thread thread = new Thread(new ThreadStart(Run));
            thread.Start();
        }
        //            
        private void Run()
        {
            while (progressBar.Value < progressBar.Maximum)
            {
                progressBar.PerformStep();
            }
        }

        private void btnInvoke_Click(object sender, EventArgs e)
        {
            progressBar.Value = 0;
            Thread thread = new Thread(new ThreadStart(RunWithInvoke));
            thread.Start();
        }
        //  Invoke        
        private void RunWithInvoke()
        {
            int value = progressBar.Value;
            while (value< progressBar.Maximum)
            {
                //        
                if (InvokeRequired)
                {
                    this.Invoke(new SetProgressBarValue(SetProgressValue), value++);
                }
                else
                {
                    progressBar.Value = ++value;
                }
            }
        }
        // SetProgressBarValue        
        private void SetProgressValue(int value)
        {
            progressBar.Value = value;
        }

        private void btnBackgroundWorker_Click(object sender, EventArgs e)
        {
            progressBar.Value = 0;
            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            //                   
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            //             
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            worker.WorkerReportsProgress = true;//        
            worker.WorkerSupportsCancellation = false;//       
            worker.RunWorkerAsync();//    
            btnBackgroundWorker.Enabled = false;
        }
        //             
        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            btnBackgroundWorker.Enabled=true;
        }
        //                   
        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //               
            progressBar.Value = e.ProgressPercentage;
        }
        //                
        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            int value = progressBar.Value;
            while (value < progressBar.Maximum)
            {
                worker.ReportProgress(++value);//    
            }
        }
    }
}

もちろん、BackgroundWorkerは上記の機能を果たすことができるほか、Systemを利用する.Windows.Forms.Timerクラスもフィールド上の機能を完了することができ、コードは以下の通りです.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace ThreadPoolDemo
{
    public partial class ThreadForm : Form
    {
        //  delegate  Invoke   
        private delegate void SetProgressBarValue(int value);
        private BackgroundWorker worker;
        public ThreadForm()
        {
            InitializeComponent();
        }

        private void btnThread_Click(object sender, EventArgs e)
        {
            progressBar.Value = 0;
            //            ,        UI         
            //CheckForIllegalCrossThreadCalls = false;
            Thread thread = new Thread(new ThreadStart(Run));
            thread.Start();
        }
        //            
        private void Run()
        {
            while (progressBar.Value < progressBar.Maximum)
            {
                progressBar.PerformStep();
            }
        }

        private void btnInvoke_Click(object sender, EventArgs e)
        {
            progressBar.Value = 0;
            Thread thread = new Thread(new ThreadStart(RunWithInvoke));
            thread.Start();
        }
        //  Invoke        
        private void RunWithInvoke()
        {
            int value = progressBar.Value;
            while (value< progressBar.Maximum)
            {
                //        
                if (InvokeRequired)
                {
                    this.Invoke(new SetProgressBarValue(SetProgressValue), value++);
                }
                else
                {
                    progressBar.Value = ++value;
                }
            }
        }
        // SetProgressBarValue        
        private void SetProgressValue(int value)
        {
            progressBar.Value = value;
        }

        private void btnBackgroundWorker_Click(object sender, EventArgs e)
        {
            progressBar.Value = 0;
            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            //                   
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            //             
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            worker.WorkerReportsProgress = true;//        
            worker.WorkerSupportsCancellation = false;//       
            worker.RunWorkerAsync();//    
            btnBackgroundWorker.Enabled = false;
        }
        //             
        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            btnBackgroundWorker.Enabled=true;
        }
        //                   
        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //               
            progressBar.Value = e.ProgressPercentage;
        }
        //                
        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            int value = progressBar.Value;
            while (value < progressBar.Maximum)
            {
                worker.ReportProgress(++value);//    
            }
        }
        //  System.Windows.Forms.Timer      
        private void btnTimer_Click(object sender, EventArgs e)
        {
            progressBar.Value = 0;
            //   .net           Timer ,      ,          
            System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
            timer.Interval = 1;
            timer.Tick += new EventHandler(timer_Tick);
            timer.Enabled = true;
        }
        //Timer         
        void timer_Tick(object sender, EventArgs e)
        {
            int value = progressBar.Value;
            if (value < progressBar.Maximum)
            {
                progressBar.Value = value+100;
            }
        }
    }
}

まとめ:本編では、マルチスレッドのUIプログラムを作成する際に参考になるスレッドを使用してWindowsアプリケーションインタフェースを操作する方法について説明します.
周公2010-01-11