Task.Run は新しいタスクを開始しますが、必ずしも新しいスレッドを作成するわけではありません。.NET のタスクスケジューラは、スレッドプール内の既存のワーカースレッドを再利用してタスクを実行します。そのため、実際の実行スレッドはスレッドプール由来であり、新規スレッドが作られるとは限りません。
長時間実行される処理に対しては、TaskCreationOptions.LongRunning を指定することで、スケジューラに「このタスクは長時間かかるため、専用スレッドで実行すべき」と伝えることができます。このオプションを使用すると、スレッドプール外の新しいスレッドが作成され、タスクが実行されます。
var longTask = Task.Factory.StartNew(() =>
{
// 長時間実行される処理
}, TaskCreationOptions.LongRunning);
このように作成されたスレッドは、スレッドプールには含まれず、Thread.CurrentThread.IsThreadPoolThread は false を返します。また、デフォルトではこのスレッドはフロントグラウンドスレッドとして動作しますが、必要に応じてバックグラウンド化も可能です。
Task.Run(() =>
{
Thread.CurrentThread.IsBackground = true;
// 処理内容
});
await を使用した非同期処理では、以下の動作が発生します:
- 現在のメソッドの実行が一時中断され、制御が呼び出し元に戻る(ブロッキングなし)
- コンパイラが状態マシンを生成し、非同期操作完了後に残りのコードを再開できるようにする
- 非同期操作が完了すると、元のコンテキスト(例:UI スレッド)に戻って後続コードが実行される(
ConfigureAwait(false)を使えばこの戻りを抑制可能)
以下は各種タスクとスレッドの特性を確認するコード例です:
// LongRunning: スレッドプール外のフロントグラウンドスレッド
await Task.Factory.StartNew(() =>
{
Console.WriteLine($"LongRunning - バックグラウンド: {Thread.CurrentThread.IsBackground}");
Console.WriteLine($"スレッドプール内: {Thread.CurrentThread.IsThreadPoolThread}");
}, TaskCreationOptions.LongRunning);
// Task.Run: スレッドプール内のバックグラウンドスレッド
await Task.Run(() =>
{
Console.WriteLine($"Task.Run - バックグラウンド: {Thread.CurrentThread.IsBackground}");
Console.WriteLine($"スレッドプール内: {Thread.CurrentThread.IsThreadPoolThread}");
});
// 手動スレッド: スレッドプール外、デフォルトはフロントグラウンド
var manualThread = new Thread(() =>
{
Console.WriteLine($"Thread - バックグラウンド: {Thread.CurrentThread.IsBackground}");
Console.WriteLine($"スレッドプール内: {Thread.CurrentThread.IsThreadPoolThread}");
});
manualThread.Start();
manualThread.Join(); // 完了待ち(実際のアプリでは注意)
UI アプリケーションでは、長時間かかる処理を UI スレッド上で同期的に実行すると、画面がフリーズします。これを避けるためには、Task.Run や async/await を活用して、重い処理をバックグラウンドで実行し、結果だけを UI スレッドで反映させるのが一般的です。
private async void OnButtonClick(object sender, EventArgs e)
{
var result = await Task.Run(() => HeavyComputation());
label.Text = result.ToString(); // UI スレッドで安全に更新
}
int HeavyComputation()
{
Thread.Sleep(3000); // 擬似的な重い処理
return 42;
}