マルチスレッド処理
一本道でしか処理できない状態を同期(synchronous)、複数の処理を平行して行える(ある処理の実行中に別の処理を止めない)状態を非同期(asynchronous)という。
処理を動かすための基本単位のことをスレッド(thread)と呼ぶ。処理の流れが一本道なものをシングルスレッド、複数の処理を平行して行うものをマルチスレッドと呼ぶ。
C#で非同期処理を行う場合、スレッドを直接作ることはほとんどない(System.Threading.Threadを使えば直接スレッドを作ることは可能)。
スレッドの新規作成やスレッド間の処理の切り替えは重たい処理なので、最小限に抑える必要がある。そこで、スレッドを直接使うのではなく、一度作ったスレッドを可能な限り使いまわす仕組み(スレッドプール)がある。
.NET Framework 4.0以降では、System.Threading.Tasks.TaskクラスやSystem.Threading.Tasks.Parallelクラスを使うことで、スレッドプールを簡単に利用することができる。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Sample
{
class Program
{
static void Main(string[] args)
{
const int N = 3;
Parallel.For(0, N, id =>
{
Random rnd = new Random();
for (int i = 0; i < 4; ++i)
{
Thread.Sleep(rnd.Next(50, 100));
Console.WriteLine("{0} (ID: {1})", i, id);
}
});
}
}
}
排他制御
マルチスレッドプログラミングで、別スレッドの実行結果を再利用するような場合には、別スレッドの処理中は処理を止める排他制御が必要になる。C#では、lockステートメントを用いることで簡単に排他制御を行うことができる。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Sample
{
class Program
{
static void Main(string[] args)
{
int num = 0;
const int ThreadNum = 5;
const int LoopNum = 5;
var syncObject = new Object();
Parallel.For(0, ThreadNum, id =>
{
for (int i = 0; i < LoopNum; ++i)
{
lock(syncObject)
{
int tmp = num;
num = tmp + 1;
}
}
});
Console.WriteLine("{0} expected:{1}", num, ThreadNum * LoopNum);
}
}
}
volatile
コンパイラは最適化の過程で無駄な変数等を削除する。しかし、マルチスレッドプログラミングでは、一見無駄に見えても、他のスレッドで値が更新される変数を用意することがある。このような変数には、volatile修飾子を付けると、コンパイラによる最適化で変数が削除されることがない。
非同期メソッド
C#5.0以上では、asyncキーワードを使うことでメソッドを非同期メソッドとして定義できる。awaitキーワードの箇所で非同期処理の完了待ちが行われる。