マルチスレッドプログラミング(4):マルチスレッドとUI操作
ユーザーの操作に迅速に対応できるように、Windowsアプリケーションを開発する際にスレッドを使用することがよくあります.時間のかかる操作では,スレッドを使用しないとUIインタフェースが長時間停滞し,ユーザが非常に見たくない状況であり,この問題を解決するためにスレッドを使用することを望んでいる.マルチスレッド操作インタフェースUIを使用するコードを次に示します.
プログラムのインタフェースは以下の通りです.「起動」ボタンをクリックしてシミュレーション操作を開始し、進捗バーに操作の全体的な進捗を表示することを目的としています.しかし、本当に「起動」ボタンをクリックすると、Systemが投げ出されるのでがっかりします.InvalidOperationException例外.例外の説明は、「スレッド間の操作が無効です.コントロール『progressBar』を作成したスレッドからアクセスします.」CheckForIllegalCrossThreadCalls属性がこのような状況になったのは.NETでは、デバッグ環境でスレッドを使用して自分で作成したUIコントロールにアクセスすることは許可されていません.これは、マルチスレッド環境でインタフェースコントロールを操作すると予知できない場合を恐れている可能性があります.開発者が自分のコード操作の境界面に問題がないことを確認できれば、比較的簡単な方法で解決することができます.CheckForIllegalCrossThreadCallsという静的プロパティを設定します.デフォルトはtrueです.falseに設定すれば、マルチスレッド環境でも操作インタフェースに異常はありません.上のコードは次のように変更できます.
これでプログラムを実行しても異常は出ません.
しかし、上記のコードを使用すると、スレッド内でインタフェースを直接操作することは許されないので、Invokeメソッドを使用することもできます.
Invokeメソッドは、インタフェースを操作するための次の例です.
このメソッドの機能は上記の操作と同じですが、CheckForIllegalCrossThreadCallsプロパティを設定する必要はありません.また、例外は投げ出されません.もちろん、上記の方法のほかに、BackgroundWorkerクラスを使用して同じ機能を完了することもできます.
BackgroundWorkerクラスの操作インタフェースBackgroundWorkerクラスの操作UIインタフェースを使用する例は周公ブログにすでに例があるので、ここでの例コード注釈は比較的簡単で、読者は周公の以前の例を見ることができ、今回使用したコード例は以下の通りである.
もちろん、BackgroundWorkerは上記の機能を果たすことができるほか、Systemを利用する.Windows.Forms.Timerクラスもフィールド上の機能を完了することができ、コードは以下の通りです.
まとめ:本編では、マルチスレッドのUIプログラムを作成する際に参考になるスレッドを使用してWindowsアプリケーションインタフェースを操作する方法について説明します.
周公2010-01-11
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