bucket-sort logo bucket-sort

プログラミングとインフラエンジニアリングの覚え書き

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] Task Parallel Library と PLINQ を理解する

[C#] Task Parallel Library と PLINQ を理解する

Jun 28, 2026 C# , .NET bucket-sort

前回は、スレッド同期と ThreadPool を見ました。

今回は、ThreadPool の上に構築される高レベルな並列処理 API を扱います。
中心になるのは Task Parallel Library、つまり TPL です。

TPL を使うと、Thread を直接作らなくても、複数の処理を並列に実行できます。

System.Threading.Tasks 名前空間

TPL の主要な型は、System.Threading.Tasks 名前空間にあります。

using System.Threading.Tasks;

代表的な型は次の通りです。

Task

非同期または並列に実行される処理を表します。戻り値がない処理に使います。

Task

戻り値を持つ処理を表します。

Parallel

データ並列やタスク並列を簡単に書くための静的クラスです。

ParallelOptions

並列度やキャンセルなど、Parallel 実行時のオプションを指定します。

CancellationToken

キャンセル要求を伝えるための値です。

Parallel クラスの役割

Parallel クラスは、ループ処理を複数スレッドで実行するための API を提供します。

代表的なのは次の 3 つです。

  • Parallel.For
  • Parallel.ForEach
  • Parallel.Invoke

大量の独立した処理を並列に実行したいときに便利です。

Parallel.For で範囲を並列処理する

using System;
using System.Threading;
using System.Threading.Tasks;

Parallel.For(0, 10, i =>
{
    Console.WriteLine($"i={i}, Thread={Thread.CurrentThread.ManagedThreadId}");
});

for 文のように見えますが、実行順序は保証されません。
並列処理では、0, 1, 2, ... の順に処理されるとは限らない点に注意します。

Parallel.ForEach でコレクションを並列処理する

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

var files = new List<string>
{
    "a.txt",
    "b.txt",
    "c.txt",
    "d.txt"
};

Parallel.ForEach(files, file =>
{
    Console.WriteLine($"{file}: Thread={Thread.CurrentThread.ManagedThreadId}");
});

Parallel.ForEach は、コレクションの各要素を並列に処理します。
画像変換、ファイル解析、数値計算など、各要素が独立している処理に向いています。

最大並列度を制御する

並列処理は、並列度を上げれば必ず速くなるわけではありません。
CPU、I/O、DB、外部 API など、どこがボトルネックかによって最適な並列度は変わります。

using System;
using System.Threading;
using System.Threading.Tasks;

var options = new ParallelOptions
{
    MaxDegreeOfParallelism = 2
};

Parallel.For(0, 10, options, i =>
{
    Console.WriteLine($"i={i}, Thread={Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(500);
});

MaxDegreeOfParallelism を使うと、同時に実行する処理数の上限を指定できます。

UI 要素へのアクセスに注意する

WPF、Windows Forms、MAUI などの UI アプリでは、UI 要素は基本的に UI スレッドから操作する必要があります。

次のような考え方が重要です。

  • 重い処理はバックグラウンドで実行する
  • UI の更新は UI スレッドに戻して行う
  • バックグラウンドスレッドから直接 UI コントロールを変更しない

WPF では Dispatcher、Windows Forms では Invoke / BeginInvoke を使って UI スレッドへ処理を戻します。

// WPF のイメージ
await Task.Run(() =>
{
    // 重い処理
});

// await 後は通常 UI スレッドに戻るため、UI 更新できる
StatusTextBlock.Text = "完了";

コンソールアプリではこの問題は目立ちませんが、UI アプリでは非常に重要です。

Task クラスの基本

Task は、ある処理の実行状態や完了を表すオブジェクトです。

using System;
using System.Threading;
using System.Threading.Tasks;

Task task = Task.Run(() =>
{
    Console.WriteLine($"Task started: {Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(1000);
    Console.WriteLine("Task finished");
});

task.Wait();

Task.Run は、処理を ThreadPool 上で実行します。
Wait() は完了まで待機しますが、現在の .NET では後述する await を使う方が自然です。

Task で結果を返す

戻り値がある処理は Task<TResult> で表せます。

using System;
using System.Threading.Tasks;

Task<int> task = Task.Run(() =>
{
    int total = 0;

    for (int i = 1; i <= 100; i++)
    {
        total += i;
    }

    return total;
});

Console.WriteLine(task.Result);

Result は結果を取得しますが、完了していない場合はブロックします。
UI アプリや ASP.NET ではデッドロックや応答性低下の原因になることがあるため、可能なら await を使います。

キャンセル要求を処理する

長時間実行される処理には、キャンセルできる設計が必要です。
.NET では CancellationTokenSource と CancellationToken を使います。

using System;
using System.Threading;
using System.Threading.Tasks;

using var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

Task worker = Task.Run(() =>
{
    for (int i = 0; i < 100; i++)
    {
        token.ThrowIfCancellationRequested();

        Console.WriteLine($"Working... {i}");
        Thread.Sleep(200);
    }
}, token);

Thread.Sleep(1000);
cts.Cancel();

try
{
    worker.Wait();
}
catch (AggregateException ex)
{
    foreach (Exception inner in ex.InnerExceptions)
    {
        Console.WriteLine(inner.GetType().Name);
    }
}

キャンセルは、外部から強制的にスレッドを止める仕組みではありません。
処理側が CancellationToken を確認し、自分で中断する協調的な仕組みです。

Parallel でキャンセルを扱う

ParallelOptions に CancellationToken を指定できます。

using System;
using System.Threading;
using System.Threading.Tasks;

using var cts = new CancellationTokenSource();

var options = new ParallelOptions
{
    CancellationToken = cts.Token,
    MaxDegreeOfParallelism = 4
};

Task.Run(() =>
{
    Thread.Sleep(1000);
    cts.Cancel();
});

try
{
    Parallel.For(0, 100, options, i =>
    {
        options.CancellationToken.ThrowIfCancellationRequested();
        Console.WriteLine(i);
        Thread.Sleep(200);
    });
}
catch (OperationCanceledException)
{
    Console.WriteLine("キャンセルされました。");
}

並列ループでも、キャンセルは協調的に扱います。

Parallel.Invoke で複数処理を並列実行する

Parallel.Invoke は、複数のメソッドを並列に実行したいときに使えます。

using System;
using System.Threading;
using System.Threading.Tasks;

Parallel.Invoke(
    () =>
    {
        Thread.Sleep(500);
        Console.WriteLine("処理 A");
    },
    () =>
    {
        Thread.Sleep(500);
        Console.WriteLine("処理 B");
    },
    () =>
    {
        Thread.Sleep(500);
        Console.WriteLine("処理 C");
    });

複数の独立した処理をまとめて実行する用途に向いています。

PLINQ とは

PLINQ(Parallel LINQ)は、LINQ クエリを並列実行する仕組みです。

通常の LINQ は、基本的に 1 つの流れで処理されます。
PLINQ では、AsParallel() を付けることで、クエリを並列実行できます。

using System;
using System.Linq;
using System.Threading;

var numbers = Enumerable.Range(1, 20);

var results = numbers
    .AsParallel()
    .Where(n =>
    {
        Console.WriteLine($"n={n}, Thread={Thread.CurrentThread.ManagedThreadId}");
        return n % 2 == 0;
    })
    .Select(n => n * n)
    .ToList();

Console.WriteLine(string.Join(", ", results));

PLINQ は、CPU 負荷の高い変換やフィルタリングに向いています。
一方で、軽すぎる処理では並列化のコストの方が大きくなることがあります。

PLINQ に明示的に参加する

PLINQ では、AsParallel() を呼び出すことで並列化に参加します。

var query = Enumerable.Range(1, 1_000_000)
    .AsParallel()
    .Where(n => n % 3 == 0)
    .Select(n => n * 2);

Console.WriteLine(query.Count());

LINQ クエリがすべて PLINQ に向くわけではありません。
順序が重要な処理、共有状態を更新する処理、I/O 待ちが中心の処理では注意が必要です。

PLINQ をキャンセルする

PLINQ でも CancellationToken を使ってキャンセルできます。

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using var cts = new CancellationTokenSource();

Task.Run(() =>
{
    Thread.Sleep(1000);
    cts.Cancel();
});

try
{
    var result = Enumerable.Range(1, 100_000_000)
        .AsParallel()
        .WithCancellation(cts.Token)
        .Where(n => n % 7 == 0)
        .Count();

    Console.WriteLine(result);
}
catch (OperationCanceledException)
{
    Console.WriteLine("PLINQ クエリがキャンセルされました。");
}

PLINQ のキャンセルも、CancellationToken を通じて協調的に行われます。

まとめ

TPL は、スレッドを直接扱わずに並列処理を書くための強力な仕組みです。

Parallel.For や Parallel.ForEach は、独立した大量の処理を並列化するのに向いています。
Task は、処理の実行と完了を表す基本単位です。
PLINQ は、LINQ クエリを並列化したいときに便利です。

次回は、Task をさらに自然に扱うための async / await を見ていきます。

C# .NET TPL Task Parallel PLINQ CancellationToken 並列処理
← [C#] スレッド同期と ThreadPool を理解する [C#] async/await による非同期処理を理解する →

Related Posts

  • [C#] async/await による非同期処理を理解する Jun 29, 2026
  • [C#] async/await の振る舞いと使い方 Mar 20, 2026
  • [C#] プラグインで拡張できるアプリケーションを設計する Jul 9, 2026
  • [C#] 属性でコードに意味を与える Jul 8, 2026

Table of Contents

  • System.Threading.Tasks 名前空間
    • Task
    • Task
    • Parallel
    • ParallelOptions
    • CancellationToken
  • Parallel クラスの役割
  • Parallel.For で範囲を並列処理する
  • Parallel.ForEach でコレクションを並列処理する
  • 最大並列度を制御する
  • UI 要素へのアクセスに注意する
  • Task クラスの基本
  • Task で結果を返す
  • キャンセル要求を処理する
  • Parallel でキャンセルを扱う
  • Parallel.Invoke で複数処理を並列実行する
  • PLINQ とは
  • PLINQ に明示的に参加する
  • PLINQ をキャンセルする
  • まとめ

Recent Posts

  • [C#] プラグインで拡張できるアプリケーションを設計する Jul 9, 2026
  • [C#] 属性でコードに意味を与える Jul 8, 2026
  • [C#] 外部アセンブリを読み込み実行時に型を呼び出す Jul 7, 2026
  • [C#] リフレクションで型の中身を調べる Jul 6, 2026
  • [C#] 型メタデータとリフレクションの基礎を理解する Jul 5, 2026

Categories

  • C#120
  • .NET119
  • AWS27
  • Laravel16
  • Linux15
  • MySQL9
  • Apache8
  • PHP8
  • DynamoDB6
  • セキュリティ6
  • Nginx5
  • WordPress4
  • インフラ4
  • Hugo3
  • .NET Framework1
  • Aurora1
  • Diagnostics1
  • Filament1
  • Git1
  • SQS1

Tags

  • C#
  • .NET
  • AWS
  • Laravel
  • コレクション
  • PHP
  • セキュリティ
  • MySQL
  • Linux
  • パフォーマンス
  • Apache
  • System.Collections.Generic
  • デリゲート
  • Code Snippet
  • DynamoDB
  • LINQ
  • NoSQL
  • PHP-FPM
  • RDS
  • System.Collections
  • Windows
  • メモリ管理
  • リフレクション
  • DoS
  • Nginx
  • WordPress
  • メタデータ
  • ラムダ式
  • 監視
  • 設計
  • Amazon Linux 2023
  • Delegate
  • Docker
  • IDisposable
  • Ipset
  • Iptables
  • LINQ to Objects
  • OPCache
  • Pointer
  • Reflection
  • System.Collections.Specialized
  • Unsafe
  • Webサーバー
  • インターフェース
  • オブジェクト指向
  • クラス設計
  • デザインパターン
  • パターンマッチング
  • ポインター
  • 継承
Powered by Hugo & Explore Theme.