FANDOM


.NET Frameworksでマルチスレッドを実現する方法として、下記のいずれかの実装を利用する方法が一般的です。

  • BackgroundWorker
  • Task
  • Thread

実装別解説 編集

BackgroundWorker 編集

BackgroundWorkerクラスを使用するとマルチスレッド化は、処理に時間がかかる実装をUIにレスポンスへ影響させずに実装することができます。
Threadクラスと比べるとシンプルなため使用頻度は非常に高いです。

public class MyWindow : Window
{
	private readonly BackgroundWorker _backgroundWorker;
 
	public MyWindow()
	{
		this._backgroundWorker = new BackgroundWorker();
		this._backgroundWorker.DoWork += OnDoWork;
		this._backgroundWorker.RunWorkerCompleted += OnRunWorkerCompleted;
 
		// 別スレッドの開始は、
		// this._backgroundWorker.RunWorkerAsync()
		// this._backgroundWorker.RunWorkerAsync("引数")
		// で行います。引数には1つだけパラメータを渡すことができます。
	}
 
 
	void OnDoWork(object sender, DoWorkEventArgs e)
	{
		var worker = sender as BackgroundWorker;
		object param = e.Argument; // RunWorkerAsyncメソッドで引数を指定した場合。
 
		// *** 注意点 ***
		// DoWorkは呼び出し元のスレッド(RunWorkerAsyncを呼び出したスレッド)とは別のスレッドで
		// 動作するスレッドです。
		// .NetFrameworksの仕様上、UIスレッドとは異なるスレッドからUIのエレメントを動作させることは
		// できません。よって、OnDoWork内ではUIエレメントのプロパティへアクセスできません。
		// アクセスが必要な場合は、RunWorkerCompletedイベントまたはProgressChangedイベントのハンドラで行います。
	}
 
	void OnRunWorkerCompleted(object sender,RunWorkerCompletedEventArgs e)
	{
		// ここはUIへアクセス可能
	}
}

BackgroundWorkerを使用する注意点として、BackgroundWorkerをネストして呼び出すことはできません。
BackgroundWorker.DoWorkイベント内で『別のBackgroundWorkerインスタンス』であったとしても、RunWorkerAsyncメソッドを呼び出すことはできません。

すでにBackgroundWorkerが動作しているかどうかをチェックするには、BackgroundWorker.IsBusyプロパティを検証します。
IsBusyプロパティはインスタンスに関係なく、BackgroundWorkerを使用できるかチェックすることができます。

// 使用できない例
BackgroundWorker worker1 = new BackgroundWorker();
BackgroundWorker worker2 = new BackgroundWorker();
 
worker1.DoWork += OnDoWork1;
worker2.DoWork += OnDoWork2;
 
void OnDoWork(object sender, DoWorkEventArgs e)
{
	// BackgroundWorkerが作成した別スレッド内から、
	// 別のBackgroundWorkerを呼び出す。
 
	worker2.RunWorkerAsync(); // 例外発生
 
	if(!worker2.IsBusy)
		worker2.RunWorkerAsync();
}
 
void OnDoWork2(object sender, DoWorkEventArgs e)
{
}
 
worker1.RunWorkerAsync();


Task 編集

var t = Task.Factory.StartNew(() =>
{
    // 何か重たい計算をして、その計算結果を返す。
    return HeavyWork();
});
 
// 計算が完了したら、そのあと続けたい処理を呼び出してもらう。
t.ContinueWith(x => Console.WriteLine(x.Result));

Thread 編集

public class MyThreadClass {
	public static void StaticMethod() {
		Console.WriteLine("静的メソッドの呼び出し");
		
		Thread.Sleep(5000); // 5秒待機
		
		Console.WriteLine("静的メソッド終了");
	}
	
	public void InstanceMethod() {
		Console.WriteLine("インスタンスメソッドの呼び出し");
		
		Thread.Sleep(5000); // 5秒待機
		
		Console.WriteLine("インスタンスメソッド終了");
	}
}

public class MainApp {
	public static int Main(String[] args) {
		// インスタンスを作成し、インスタンスメソッドを別スレッドで実行
		MyThreadClass obj = new MyThreadClass();
		
		Thread instanceThread = new Thread( obj.InstanceMethod );
		instanceThread.Start();
		
		// 静的メソッドを別スレッドで実行
		Thread staticThread = new Thread( MyThreadClass.StaticMethod );
		staticThread.Start();
		
		return 0;
	}
}


スレッドタイマー 編集

スレッドを使って一定間隔で行いたい処理を実行することができます。

Timerはインスタンスが存在する間だけ一定周期でデリゲートを呼び出します。
下記のRunTimer()でローカル変数としてTimerを作成した場合、ガベージコレクションにより不特定のタイミングでTimerが破棄され一定周期での呼び出しが終了する可能性があります。


System.Threading.Timer TimerItem;
 
// 一定間隔で呼び出す処理
private void TimerTask(object param)
{
	// ここは別スレッドで実行されるので、時間がかかる処理を行っても良い
}
 
 
public void RunTimer()
{
	// 一定周期で行いたい処理を設定
	System.Threading.TimerCallback TimerDelegate =
		new System.Threading.TimerCallback(TimerTask);
 
	string param = "何らかのパラメーター";
 
	// 一定周期でTimerDelegateを呼び出すTimerを作成
	// 2000msec後からタイマーを開始し、2000msec間隔でTimerDelegateを呼び出す。
	TimerItem =
		new System.Threading.Timer(TimerDelegate, param, 2000, 2000);
 
	// ↑ TimerItemはインスタンスを保持できるように、メンバ変数などにしておく。
 
}

マルチスレッドシーケンス 編集

スレッドを立てて、そのスレッドで処理を順番に実行する方法です。
シンプルに考えれば、スレッド内で処理の数だけループをすればいいだけですが、ループ処理をしてしまうとどこかの処理で例外が発生し処理を中断する場合の処理が必要となるため、ここでは各処理が次の処理を呼び出すシーケンス構造で実装します。

public class SequencialThreadTest {
	int sequencePos = 0;
	List<WaitCallback> sequences = new List<WaitCallback>();
 
	public void Start() {
		this.sequencePos = 0;
 
		// [シーケンス呼び出しを行う処理]
		this.sequences.Clear();
		this.sequences.Add( new WaitCallback(SequencialThreadTest.Test1));
		this.sequences.Add( new WaitCallback(SequencialThreadTest.Test2));
		this.sequences.Add( new WaitCallback(SequencialThreadTest.Test3));
 
		// シーケンス開始
		ThreadPool.QueueUserWorkItem(this.sequences[sequencePos], this);
	}
 
 
 
	static void Join(object state) // 合流処理
	{
 
		var @this = state as SequencialThreadTest;
		lock (@this)
		{
			@this.sequencePos++;
 
			if (@this.sequences.Count <= @this.sequencePos)
			{
				// 次のシーケンスがない場合の処理
			}
			else
			{
				var waitCallback = @this.sequences[@this.pos];
				ThreadPool.QueueUserWorkItem(waitCallback, state);
			}
		}
	}
 
	static void Test1(object info) {
		var @this = info as SequencialThreadTest;
 
		// TODO:スレッドで行いたい処理
 
		Join(@this); // 次のシーケンスを呼び出し
	}
 
	static void Test2(object info) {
		var @this = info as SequencialThreadTest;
 
		// TODO:スレッドで行いたい処理
 
		Join(@this); // 次のシーケンスを呼び出し
	}
 
	static void Test3(object info) {
		var @this = info as SequencialThreadTest;
 
		// TODO:スレッドで行いたい処理
 
		Join(@this); // 次のシーケンスを呼び出し
	}
 
}

参照 編集

広告ブロッカーが検出されました。


広告収入で運営されている無料サイトWikiaでは、このたび広告ブロッカーをご利用の方向けの変更が加わりました。

広告ブロッカーが改変されている場合、Wikiaにアクセスしていただくことができなくなっています。カスタム広告ブロッカーを解除してご利用ください。

FANDOMでも見てみる

おまかせWiki