次の方法で共有


タスク ベースの非同期プログラミング

タスク並列ライブラリ (TPL) は、非同期操作を表す タスクの概念に基づいています。 ある意味では、タスクはスレッドや ThreadPool 作業項目に似ていますが、抽象化のレベルは高くなります。 タスクの並列処理 という用語は、同時に実行される 1 つ以上の独立したタスクを指します。 タスクには、次の 2 つの主な利点があります。

  • システム リソースのより効率的でスケーラブルな使用。

    バックグラウンドでは、タスクは ThreadPoolにキューに入れられます。これは、スレッドの数を決定して調整するアルゴリズムによって強化されています。 これらのアルゴリズムは、スループットを最大化するための負荷分散を提供します。 このプロセスにより、タスクは比較的軽量になり、その多くを作成してきめ細かい並列処理を可能にすることができます。

  • スレッドまたは作業項目で可能なよりも多くのプログラムによる制御。

    タスクとその周囲に構築されたフレームワークは、待機、キャンセル、継続、堅牢な例外処理、詳細な状態、カスタム スケジュールなどをサポートする豊富な API セットを提供します。

どちらの理由でも、TPL は、.NET でマルチスレッド、非同期、および並列コードを記述する場合に推奨される API です。

タスクの暗黙的な作成と実行

Parallel.Invoke メソッドは、任意の数のステートメントを同時に実行する便利な方法を提供します。 作業項目ごとに Action デリゲートを渡すだけです。 これらのデリゲートを作成する最も簡単な方法は、ラムダ式を使用する方法です。 ラムダ式は、名前付きメソッドを呼び出すか、コードをインラインで提供できます。 次の例は、同時に実行される 2 つのタスクを作成して開始する基本的な Invoke 呼び出しを示しています。 最初のタスクは、DoSomeWorkという名前のメソッドを呼び出すラムダ式で表され、2 番目のタスクは、DoSomeOtherWorkという名前のメソッドを呼び出すラムダ式によって表されます。

手記

このドキュメントでは、ラムダ式を使用して TPL でデリゲートを定義します。 C# または Visual Basic のラムダ式に慣れていない場合は、PLINQ と TPL のラムダ式のを参照してください。

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())

手記

Invoke によってバックグラウンドで作成された Task インスタンスの数は、提供されるデリゲートの数と必ずしも等しいとは限りません。 TPL では、特に多数のデリゲートを使用して、さまざまな最適化が採用される場合があります。

詳細については、「方法: Parallel.Invoke を使用して並列操作を実行する」を参照してください。

タスクの実行をより詳細に制御したり、タスクから値を返したりするには、Task オブジェクトをより明示的に操作する必要があります。

タスクの明示的な作成と実行

値を返さないタスクは、System.Threading.Tasks.Task クラスによって表されます。 値を返すタスクは、Taskから継承される System.Threading.Tasks.Task<TResult> クラスによって表されます。 タスク オブジェクトは、インフラストラクチャの詳細を処理し、タスクの有効期間を通じて呼び出し元のスレッドからアクセスできるメソッドとプロパティを提供します。 たとえば、常にタスクの Status プロパティにアクセスすることで、タスクが実行を開始したか、完了したか、キャンセルされたか、あるいは例外が発生したかを判断することができます。 状態は、TaskStatus 列挙体によって表されます。

タスクを作成するときに、タスクが実行するコードをカプセル化するユーザー デリゲートをユーザーに付与します。 デリゲートは、名前付きデリゲート、匿名メソッド、またはラムダ式として表すことができます。 ラムダ式には、次の例に示すように、名前付きメソッドの呼び出しを含めることができます。 この例には、コンソール モード アプリケーションが終了する前にタスクが実行を完了するように、Task.Wait メソッドの呼び出しが含まれています。

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

public class Lambda
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Create a task and supply a user delegate by using a lambda expression.
      Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
      // Start the task.
      taskA.Start();

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                        Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Lambda
    Module Example
        Public Sub Main()
            Thread.CurrentThread.Name = "Main"

            ' Create a task and supply a user delegate by using a lambda expression. 
            Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
            ' Start the task.
            taskA.Start()

            ' Output a message from the calling thread.
            Console.WriteLine("Hello from thread '{0}'.",
                            Thread.CurrentThread.Name)
            taskA.Wait()
        End Sub
    End Module
    ' The example displays output like the following:
    '    Hello from thread 'Main'.
    '    Hello from taskA.
End Namespace

Task.Run メソッドを使用して、1 回の操作でタスクを作成および開始することもできます。 タスクを管理するために、Run メソッドは、現在のスレッドに関連付けられているタスク スケジューラに関係なく、既定のタスク スケジューラを使用します。 タスクの作成とスケジュール設定をより詳細に制御する必要がない場合は、Run メソッドを使用してタスクを作成および開始することをお勧めします。

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

namespace Run;

public class Example
{
   public static void Main()
   {
      Thread.CurrentThread.Name = "Main";

      // Define and run the task.
      Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

      // Output a message from the calling thread.
      Console.WriteLine("Hello from thread '{0}'.",
                          Thread.CurrentThread.Name);
      taskA.Wait();
   }
}
// The example displays output as follows:
//       Hello from thread 'Main'.
//       Hello from taskA.
// or
//       Hello from taskA.
//       Hello from thread 'Main'.
Imports System.Threading

Namespace Run
    Module Example
        Public Sub Main()
            Thread.CurrentThread.Name = "Main"

            Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

            ' Output a message from the calling thread.
            Console.WriteLine("Hello from thread '{0}'.",
                            Thread.CurrentThread.Name)
            taskA.Wait()
        End Sub
    End Module
    ' The example displays output like the following:
    '    Hello from thread 'Main'.
    '    Hello from taskA.
End Namespace

TaskFactory.StartNew メソッドを使用して、1 回の操作でタスクを作成して開始することもできます。 次の例に示すように、次の場合にこのメソッドを使用できます。

  • 作成とスケジュールを分離する必要はありません。追加のタスク作成オプションまたは特定のスケジューラの使用が必要です。

  • Task.AsyncState プロパティを使用して取得できる追加の状態をタスクに渡す必要があります。

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

namespace TaskIntro;

class CustomData
{
    public long CreationTime;
    public int Name;
    public int ThreadNum;
}

public class AsyncState
{
    public static void Main()
    {
        Task[] taskArray = new Task[10];
        for (int i = 0; i < taskArray.Length; i++)
        {
            taskArray[i] = Task.Factory.StartNew((Object obj) =>
            {
                CustomData data = obj as CustomData;
                if (data == null) return;

                data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
            },
            new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });
        }
        Task.WaitAll(taskArray);
        foreach (var task in taskArray)
        {
            var data = task.AsyncState as CustomData;
            if (data != null)
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum);
        }
    }
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)

            For Each task In taskArray
                Dim data = TryCast(task.AsyncState, CustomData)
                If data IsNot Nothing Then
                    Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                    data.Name, data.CreationTime, data.ThreadNum)
                End If
            Next
        End Sub
    End Module
    ' The example displays output like the following:
    '     Task #0 created at 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

TaskTask<TResult> は、メソッドを Task.Factory.StartNew()として呼び出すことができるように、TaskFactoryの既定のインスタンスを返す静的な Factory プロパティを公開します。 また、次の例では、タスクは System.Threading.Tasks.Task<TResult>型であるため、それぞれのタスクには、計算の結果を含むパブリック Task<TResult>.Result プロパティがあります。 タスクは非同期的に実行され、任意の順序で完了する可能性があります。 計算が完了する前に Result プロパティにアクセスすると、値が使用可能になるまで呼び出し元のスレッドがブロックされます。

using System;
using System.Threading.Tasks;

public class Result
{
   public static void Main()
   {
        Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(100.0)),
                                     Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

        var results = new Double[taskArray.Length];
        Double sum = 0;

        for (int i = 0; i < taskArray.Length; i++) {
            results[i] = taskArray[i].Result;
            Console.Write("{0:N1} {1}", results[i],
                              i == taskArray.Length - 1 ? "= " : "+ ");
            sum += results[i];
        }
        Console.WriteLine("{0:N1}", sum);
   }

   private static Double DoComputation(Double start)
   {
      Double sum = 0;
      for (var value = start; value <= start + 10; value += .1)
         sum += value;

      return sum;
   }
}
// The example displays the following output:
//        606.0 + 10,605.0 + 100,495.0 = 111,706.0

Namespace Result
    Module Example
        Public Sub Main()
            Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
                Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)),
                Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0))}

            Dim results(taskArray.Length - 1) As Double
            Dim sum As Double

            For i As Integer = 0 To taskArray.Length - 1
                results(i) = taskArray(i).Result
                Console.Write("{0:N1} {1}", results(i),
                    If(i = taskArray.Length - 1, "= ", "+ "))
                sum += results(i)
            Next
            Console.WriteLine("{0:N1}", sum)
        End Sub

        Private Function DoComputation(start As Double) As Double
            Dim sum As Double
            For value As Double = start To start + 10 Step .1
                sum += value
            Next
            Return sum
        End Function
    End Module
    ' The example displays the following output:
    '       606.0 + 10,605.0 + 100,495.0 = 111,706.0
End Namespace

詳細については、「方法: タスクから値を返す」を参照してください。

ラムダ式を使用してデリゲートを作成すると、ソース コード内のその時点で表示されるすべての変数にアクセスできます。 ただし、特にループ内では、ラムダによって変数が想定どおりにキャプチャされない場合があります。 反復のたびに変更されるため、値ではなく変数の参照のみをキャプチャします。 次の例は、この問題を示しています。 CustomData オブジェクトをインスタンス化し、ループ カウンターをオブジェクトの識別子として使用するラムダ式にループ カウンターを渡します。 この例の出力に示すように、各 CustomData オブジェクトには同じ識別子があります。

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

namespace Example.Iterations;

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class IterationTwo
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in the loop
      // counter. This produces an unexpected result.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj) => {
                                                 var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};
                                                 data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                 Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                              i );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #10 created at 635116418427727841 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427737842 on thread #4.
//       Task #10 created at 635116418427727841 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427747843 on thread #3.
//       Task #10 created at 635116418427737842 on thread #4.
Imports System.Threading

Namespace IterationsTwo
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            ' Create the task object by using an Action(Of Object) to pass in the loop
            ' counter. This produces an unexpected result.
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks}
                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    i)
            Next
            Task.WaitAll(taskArray)
        End Sub
    End Module
    ' The example displays output like the following:
    '       Task #10 created at 635116418427727841 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427737842 on thread #4.
    '       Task #10 created at 635116418427727841 on thread #3.
    '       Task #10 created at 635116418427747843 on thread #3.
    '       Task #10 created at 635116418427747843 on thread #3.
    '       Task #10 created at 635116418427737842 on thread #4.
End Namespace

コンストラクターを介してタスクに状態オブジェクトを提供することで、各イテレーションの値にアクセスできます。 次の例では、CustomData オブジェクトを作成するときにループ カウンターを使用して前の例を変更し、ラムダ式に渡します。 この例の出力に示すように、各 CustomData オブジェクトには、オブジェクトがインスタンス化された時点でのループ カウンターの値に基づく一意の識別子が含まれるようになりました。

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

class CustomData
{
   public long CreationTime;
   public int Name;
   public int ThreadNum;
}

public class IterationOne
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data
      // to the Task constructor. This is useful when you need to capture outer variables
      // from within a loop.
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null)
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                                  Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                   data.Name, data.CreationTime, data.ThreadNum);
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);
   }
}
// The example displays output like the following:
//       Task #0 created at 635116412924597583 on thread #3.
//       Task #1 created at 635116412924607584 on thread #4.
//       Task #3 created at 635116412924607584 on thread #4.
//       Task #4 created at 635116412924607584 on thread #4.
//       Task #2 created at 635116412924607584 on thread #3.
//       Task #6 created at 635116412924607584 on thread #3.
//       Task #5 created at 635116412924607584 on thread #4.
//       Task #8 created at 635116412924607584 on thread #4.
//       Task #7 created at 635116412924607584 on thread #3.
//       Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading

Namespace IterationsOne
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            ' Create the task object by using an Action(Of Object) to pass in custom data
            ' to the Task constructor. This is useful when you need to capture outer variables
            ' from within a loop. 
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
                                                                         data.Name, data.CreationTime, data.ThreadNum)
                                                     End Sub,
                    New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)
        End Sub
    End Module
    ' The example displays output like the following:
    '       Task #0 created at 635116412924597583 on thread #3.
    '       Task #1 created at 635116412924607584 on thread #4.
    '       Task #3 created at 635116412924607584 on thread #4.
    '       Task #4 created at 635116412924607584 on thread #4.
    '       Task #2 created at 635116412924607584 on thread #3.
    '       Task #6 created at 635116412924607584 on thread #3.
    '       Task #5 created at 635116412924607584 on thread #4.
    '       Task #8 created at 635116412924607584 on thread #4.
    '       Task #7 created at 635116412924607584 on thread #3.
    '       Task #9 created at 635116412924607584 on thread #4.
End Namespace

この状態は、タスク デリゲートに引数として渡され、Task.AsyncState プロパティを使用してタスク オブジェクトからアクセスできます。 次の例は、前の例のバリエーションです。 AsyncState プロパティを使用して、ラムダ式に渡される CustomData オブジェクトに関する情報を表示します。

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

namespace TaskIntro;

class CustomData
{
    public long CreationTime;
    public int Name;
    public int ThreadNum;
}

public class AsyncState
{
    public static void Main()
    {
        Task[] taskArray = new Task[10];
        for (int i = 0; i < taskArray.Length; i++)
        {
            taskArray[i] = Task.Factory.StartNew((Object obj) =>
            {
                CustomData data = obj as CustomData;
                if (data == null) return;

                data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
            },
            new CustomData() { Name = i, CreationTime = DateTime.Now.Ticks });
        }
        Task.WaitAll(taskArray);
        foreach (var task in taskArray)
        {
            var data = task.AsyncState as CustomData;
            if (data != null)
                Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                  data.Name, data.CreationTime, data.ThreadNum);
        }
    }
}
// The example displays output like the following:
//     Task #0 created at 635116412924597583, ran on thread #3.
//     Task #1 created at 635116412924607584, ran on thread #4.
//     Task #2 created at 635116412924607584, ran on thread #4.
//     Task #3 created at 635116412924607584, ran on thread #4.
//     Task #4 created at 635116412924607584, ran on thread #3.
//     Task #5 created at 635116412924607584, ran on thread #3.
//     Task #6 created at 635116412924607584, ran on thread #4.
//     Task #7 created at 635116412924607584, ran on thread #4.
//     Task #8 created at 635116412924607584, ran on thread #3.
//     Task #9 created at 635116412924607584, ran on thread #4.
Imports System.Threading

Namespace AsyncState
    Class CustomData
        Public CreationTime As Long
        Public Name As Integer
        Public ThreadNum As Integer
    End Class

    Module Example
        Public Sub Main()
            Dim taskArray(9) As Task
            For i As Integer = 0 To taskArray.Length - 1
                taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
                                                         Dim data As CustomData = TryCast(obj, CustomData)
                                                         If data Is Nothing Then Return

                                                         data.ThreadNum = Environment.CurrentManagedThreadId
                                                     End Sub,
                New CustomData With {.Name = i, .CreationTime = Date.Now.Ticks})
            Next
            Task.WaitAll(taskArray)

            For Each task In taskArray
                Dim data = TryCast(task.AsyncState, CustomData)
                If data IsNot Nothing Then
                    Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                                    data.Name, data.CreationTime, data.ThreadNum)
                End If
            Next
        End Sub
    End Module
    ' The example displays output like the following:
    '     Task #0 created at 635116412924597583, ran on thread #3.
    '     Task #1 created at 635116412924607584, ran on thread #4.
    '     Task #2 created at 635116412924607584, ran on thread #4.
    '     Task #3 created at 635116412924607584, ran on thread #4.
    '     Task #4 created at 635116412924607584, ran on thread #3.
    '     Task #5 created at 635116412924607584, ran on thread #3.
    '     Task #6 created at 635116412924607584, ran on thread #4.
    '     Task #7 created at 635116412924607584, ran on thread #4.
    '     Task #8 created at 635116412924607584, ran on thread #3.
    '     Task #9 created at 635116412924607584, ran on thread #4.
End Namespace

タスク ID

すべてのタスクは、アプリケーション ドメインで一意に識別される整数 ID を受け取り、Task.Id プロパティを使用してアクセスできます。 この ID は、Visual Studio デバッガー 並列スタック および タスク ウィンドウでタスク情報を表示する場合に便利です。 ID は遅延して作成されます。つまり、要求されるまで作成されません。 そのため、プログラムを実行するたびにタスクの ID が異なる場合があります。 デバッガーでタスク ID を表示する方法の詳細については、「タスク ウィンドウの の使用」および「並列スタック ウィンドウ を使用する」を参照してください。

タスクの作成オプション

タスクを作成するほとんどの API は、TaskCreationOptions パラメーターを受け取るオーバーロードを提供します。 これらのオプションの 1 つ以上を指定することで、タスク スケジューラにスレッド プールでタスクをスケジュールする方法を指示します。 オプションは、ビットごとの または 操作を使用して結合できます。

次の例は、LongRunning オプションと PreferFairness オプションを持つタスクを示しています。

var task3 = new Task(() => MyLongRunningMethod(),
                    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Dim task3 = New Task(Sub() MyLongRunningMethod(),
                        TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

タスク、スレッド、カルチャー

各スレッドには、関連付けられたカルチャと UI カルチャがあり、それぞれ Thread.CurrentCulture プロパティと Thread.CurrentUICulture プロパティによって定義されます。 スレッドのカルチャは、書式設定、解析、並べ替え、文字列比較操作などの操作で使用されます。 スレッドの UI カルチャは、リソース参照で使用されます。

システム カルチャは、スレッドの既定のカルチャと UI カルチャを定義します。 ただし、CultureInfo.DefaultThreadCurrentCulture プロパティと CultureInfo.DefaultThreadCurrentUICulture プロパティを使用して、アプリケーション ドメイン内のすべてのスレッドに既定のカルチャを指定できます。 スレッドのカルチャを明示的に設定して新しいスレッドを起動した場合、新しいスレッドは呼び出し元スレッドのカルチャを継承しません。代わりに、そのカルチャが既定のシステム カルチャです。 ただし、タスク ベースのプログラミングでは、タスクが別のスレッドで非同期的に実行される場合でも、タスクは呼び出し元スレッドのカルチャを使用します。

次の例では、簡単な図を示します。 アプリの現在のカルチャがフランス語 (フランス) に変更されます。 フランス語 (フランス) が既に現在のカルチャである場合は、英語 (米国) に変わります。 その後、新しいカルチャで通貨値として書式設定された数値を返す formatDelegate という名前のデリゲートを呼び出します。 デリゲートがタスクによって同期的または非同期的に呼び出されるかどうかに関係なく、タスクは呼び出し元スレッドのカルチャを使用します。

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

public class Example
{
   public static void Main()
   {
       decimal[] values = { 163025412.32m, 18905365.59m };
       string formatString = "C2";
       Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture on thread {1}.\n",
                                                                           CultureInfo.CurrentCulture.Name,
                                                                           Thread.CurrentThread.ManagedThreadId);
                                             foreach (var value in values)
                                                output += String.Format("{0}   ", value.ToString(formatString));

                                             output += Environment.NewLine;
                                             return output;
                                           };

       Console.WriteLine("The example is running on thread {0}",
                         Thread.CurrentThread.ManagedThreadId);
       // Make the current culture different from the system culture.
       Console.WriteLine("The current culture is {0}",
                         CultureInfo.CurrentCulture.Name);
       if (CultureInfo.CurrentCulture.Name == "fr-FR")
          Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
       else
          Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

       Console.WriteLine("Changed the current culture to {0}.\n",
                         CultureInfo.CurrentCulture.Name);

       // Execute the delegate synchronously.
       Console.WriteLine("Executing the delegate synchronously:");
       Console.WriteLine(formatDelegate());

       // Call an async delegate to format the values using one format string.
       Console.WriteLine("Executing a task asynchronously:");
       var t1 = Task.Run(formatDelegate);
       Console.WriteLine(t1.Result);

       Console.WriteLine("Executing a task synchronously:");
       var t2 = new Task<String>(formatDelegate);
       t2.RunSynchronously();
       Console.WriteLine(t2.Result);
   }
}
// The example displays the following output:
//         The example is running on thread 1
//         The current culture is en-US
//         Changed the current culture to fr-FR.
//
//         Executing the delegate synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task asynchronously:
//         Formatting using the fr-FR culture on thread 3.
//         163 025 412,32 €   18 905 365,59 €
//
//         Executing a task synchronously:
//         Formatting using the fr-FR culture on thread 1.
//         163 025 412,32 €   18 905 365,59 €
Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim values() As Decimal = {163025412.32D, 18905365.59D}
        Dim formatString As String = "C2"
        Dim formatDelegate As Func(Of String) = Function()
                                                    Dim output As String = String.Format("Formatting using the {0} culture on thread {1}.",
                                                                                         CultureInfo.CurrentCulture.Name,
                                                                                         Thread.CurrentThread.ManagedThreadId)
                                                    output += Environment.NewLine
                                                    For Each value In values
                                                        output += String.Format("{0}   ", value.ToString(formatString))
                                                    Next
                                                    output += Environment.NewLine
                                                    Return output
                                                End Function

        Console.WriteLine("The example is running on thread {0}",
                          Thread.CurrentThread.ManagedThreadId)
        ' Make the current culture different from the system culture.
        Console.WriteLine("The current culture is {0}",
                          CultureInfo.CurrentCulture.Name)
        If CultureInfo.CurrentCulture.Name = "fr-FR" Then
            Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Else
            Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")
        End If
        Console.WriteLine("Changed the current culture to {0}.",
                          CultureInfo.CurrentCulture.Name)
        Console.WriteLine()

        ' Execute the delegate synchronously.
        Console.WriteLine("Executing the delegate synchronously:")
        Console.WriteLine(formatDelegate())

        ' Call an async delegate to format the values using one format string.
        Console.WriteLine("Executing a task asynchronously:")
        Dim t1 = Task.Run(formatDelegate)
        Console.WriteLine(t1.Result)

        Console.WriteLine("Executing a task synchronously:")
        Dim t2 = New Task(Of String)(formatDelegate)
        t2.RunSynchronously()
        Console.WriteLine(t2.Result)
    End Sub
End Module

' The example displays the following output:
'
'          The example is running on thread 1
'          The current culture is en-US
'          Changed the current culture to fr-FR.
'
'          Executing the delegate synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task asynchronously:
'          Formatting Imports the fr-FR culture on thread 3.
'          163 025 412,32 €   18 905 365,59 €
'
'          Executing a task synchronously:
'          Formatting Imports the fr-FR culture on thread 1.
'          163 025 412,32 €   18 905 365,59 €

手記

.NET Framework 4.6 より前のバージョンの .NET Framework では、タスクのカルチャは、呼び出し元スレッドのカルチャではなく、実行されるスレッドのカルチャによって決まります。 非同期タスクの場合、タスクで使用されるカルチャは、呼び出し元スレッドのカルチャとは異なる場合があります。

非同期タスクとカルチャの詳細については、CultureInfo 記事の「カルチャと非同期タスク ベースの操作」セクションを参照してください。

タスクの継続の作成

Task.ContinueWith メソッドと Task<TResult>.ContinueWith メソッドを使用すると、継続タスク が完了したときに開始するタスクを指定できます。 継続タスクのデリゲートは、継続元タスクの状態を調べることができるように、継続元タスクへの参照を渡します。 また、Task<TResult>.Result プロパティの値を取得することで、継続元の出力を継続の入力として使用できます。

次の例では、getData タスクは、TaskFactory.StartNew<TResult>(Func<TResult>) メソッドの呼び出しによって開始されます。 processData タスクは、getData が完了すると自動的に開始され、processData が完了すると displayData が開始されます。 getData は、getData タスクの Task<TResult>.Result プロパティを介して processData タスクからアクセスできる整数配列を生成します。 processData タスクは、その配列を処理し、Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>) メソッドに渡されたラムダ式の戻り値の型から型が推論された結果を返します。 displayData タスクは、processData が完了すると自動的に実行され、processData ラムダ式によって返される Tuple<T1,T2,T3> オブジェクトは、processData タスクの Task<TResult>.Result プロパティを介して displayData タスクからアクセスできます。 displayData タスクは、processData タスクの結果を受け取ります。 同様の方法で型が推論され、Result プロパティでプログラムで使用できる結果が生成されます。

using System;
using System.Threading.Tasks;

public class ContinuationOne
{
   public static void Main()
   {
      var getData = Task.Factory.StartNew(() => {
                                             Random rnd = new Random();
                                             int[] values = new int[100];
                                             for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                values[ctr] = rnd.Next();

                                             return values;
                                          } );
      var processData = getData.ContinueWith((x) => {
                                                int n = x.Result.Length;
                                                long sum = 0;
                                                double mean;

                                                for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                                   sum += x.Result[ctr];

                                                mean = sum / (double) n;
                                                return Tuple.Create(n, sum, mean);
                                             } );
      var displayData = processData.ContinueWith((x) => {
                                                    return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                         x.Result.Item1, x.Result.Item2,
                                                                         x.Result.Item3);
                                                 } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Namespace ContinuationsOne
    Module Example
        Public Sub Main()
            Dim getData = Task.Factory.StartNew(Function()
                                                    Dim rnd As New Random()
                                                    Dim values(99) As Integer
                                                    For ctr = 0 To values.GetUpperBound(0)
                                                        values(ctr) = rnd.Next()
                                                    Next
                                                    Return values
                                                End Function)
            Dim processData = getData.ContinueWith(Function(x)
                                                       Dim n As Integer = x.Result.Length
                                                       Dim sum As Long
                                                       Dim mean As Double

                                                       For ctr = 0 To x.Result.GetUpperBound(0)
                                                           sum += x.Result(ctr)
                                                       Next
                                                       mean = sum / n
                                                       Return Tuple.Create(n, sum, mean)
                                                   End Function)
            Dim displayData = processData.ContinueWith(Function(x)
                                                           Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                                                   x.Result.Item1, x.Result.Item2,
                                                                                   x.Result.Item3)
                                                       End Function)
            Console.WriteLine(displayData.Result)
        End Sub
    End Module
    ' The example displays output like the following:
    '   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
End Namespace

Task.ContinueWith はインスタンス メソッドであるため、継続するタスクごとに Task<TResult> オブジェクトをインスタンス化する代わりに、メソッド呼び出しを連結できます。 次の例は、前の例と機能的に同じですが、Task.ContinueWith メソッドの呼び出しを連結する点が異なります。 メソッド呼び出しのチェーンによって返される Task<TResult> オブジェクトは、最後の継続タスクです。

using System;
using System.Threading.Tasks;

public class ContinuationTwo
{
   public static void Main()
   {
      var displayData = Task.Factory.StartNew(() => {
                                                 Random rnd = new Random();
                                                 int[] values = new int[100];
                                                 for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
                                                    values[ctr] = rnd.Next();

                                                 return values;
                                              } ).
                        ContinueWith((x) => {
                                        int n = x.Result.Length;
                                        long sum = 0;
                                        double mean;

                                        for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)
                                           sum += x.Result[ctr];

                                        mean = sum / (double) n;
                                        return Tuple.Create(n, sum, mean);
                                     } ).
                        ContinueWith((x) => {
                                        return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                             x.Result.Item1, x.Result.Item2,
                                                             x.Result.Item3);
                                     } );
      Console.WriteLine(displayData.Result);
   }
}
// The example displays output similar to the following:
//    N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Namespace ContinuationsTwo
    Module Example
        Public Sub Main()
            Dim displayData = Task.Factory.StartNew(Function()
                                                        Dim rnd As New Random()
                                                        Dim values(99) As Integer
                                                        For ctr = 0 To values.GetUpperBound(0)
                                                            values(ctr) = rnd.Next()
                                                        Next
                                                        Return values
                                                    End Function). _
            ContinueWith(Function(x)
                             Dim n As Integer = x.Result.Length
                             Dim sum As Long
                             Dim mean As Double

                             For ctr = 0 To x.Result.GetUpperBound(0)
                                 sum += x.Result(ctr)
                             Next
                             mean = sum / n
                             Return Tuple.Create(n, sum, mean)
                         End Function). _
            ContinueWith(Function(x)
                             Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
                                                 x.Result.Item1, x.Result.Item2,
                                                 x.Result.Item3)
                         End Function)
            Console.WriteLine(displayData.Result)
        End Sub
    End Module
    ' The example displays output like the following:
    '   N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
End Namespace

ContinueWhenAll メソッドと ContinueWhenAny メソッドを使用すると、複数のタスクから続行できます。

詳細については、「継続タスクを使用したタスクの連結」を参照してください。

独立した子タスクの作成

タスクで実行されているユーザー コードが新しいタスクを作成し、AttachedToParent オプションを指定しない場合、新しいタスクは特別な方法で親タスクと同期されません。 この種類の同期されていないタスクは、デタッチされた入れ子タスク またはデタッチされた子タスク と呼ばれます。 次の例は、デタッチされた子タスクを 1 つ作成するタスクを示しています。

var outer = Task.Factory.StartNew(() =>
{
    Console.WriteLine("Outer task beginning.");

    var child = Task.Factory.StartNew(() =>
    {
        Thread.SpinWait(5000000);
        Console.WriteLine("Detached task completed.");
    });
});

outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
//    Outer task beginning.
//    Outer task completed.
//    Detached task completed.
Dim outer = Task.Factory.StartNew(Sub()
                                      Console.WriteLine("Outer task beginning.")
                                      Dim child = Task.Factory.StartNew(Sub()
                                                                            Thread.SpinWait(5000000)
                                                                            Console.WriteLine("Detached task completed.")
                                                                        End Sub)
                                  End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
'     Outer task beginning.
'     Outer task completed.
'    Detached child completed.

手記

親タスクはデタッチされた子タスクの終了を待機しません。

子タスクの作成

タスクで実行されているユーザー コードが AttachedToParent オプションを使用してタスクを作成する場合、新しいタスクは親タスクの アタッチされた子タスク と呼ばれます。 AttachedToParent オプションを使用すると、親タスクがアタッチされているすべての子タスクの完了を暗黙的に待機するため、構造化タスクの並列処理を表すことができます。 次の例は、10 個のアタッチされた子タスクを作成する親タスクを示しています。 この例では、Task.Wait メソッドを呼び出して、親タスクが完了するまで待機します。 アタッチされた子タスクが完了するまで明示的に待機する必要はありません。

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

public class Child
{
   public static void Main()
   {
      var parent = Task.Factory.StartNew(() => {
                      Console.WriteLine("Parent task beginning.");
                      for (int ctr = 0; ctr < 10; ctr++) {
                         int taskNo = ctr;
                         Task.Factory.StartNew((x) => {
                                                  Thread.SpinWait(5000000);
                                                  Console.WriteLine("Attached child #{0} completed.",
                                                                    x);
                                               },
                                               taskNo, TaskCreationOptions.AttachedToParent);
                      }
                   });

      parent.Wait();
      Console.WriteLine("Parent task completed.");
   }
}
// The example displays output like the following:
//       Parent task beginning.
//       Attached child #9 completed.
//       Attached child #0 completed.
//       Attached child #8 completed.
//       Attached child #1 completed.
//       Attached child #7 completed.
//       Attached child #2 completed.
//       Attached child #6 completed.
//       Attached child #3 completed.
//       Attached child #5 completed.
//       Attached child #4 completed.
//       Parent task completed.
Imports System.Threading

Namespace Child
    Module Example
        Public Sub Main()
            Dim parent = Task.Factory.StartNew(Sub()
                                                   Console.WriteLine("Parent task beginning.")
                                                   For ctr As Integer = 0 To 9
                                                       Dim taskNo As Integer = ctr
                                                       Task.Factory.StartNew(Sub(x)
                                                                                 Thread.SpinWait(5000000)
                                                                                 Console.WriteLine("Attached child #{0} completed.",
                                                                                                 x)
                                                                             End Sub,
                                                       taskNo, TaskCreationOptions.AttachedToParent)
                                                   Next
                                               End Sub)
            parent.Wait()
            Console.WriteLine("Parent task completed.")
        End Sub
    End Module
    ' The example displays output like the following:
    '       Parent task beginning.
    '       Attached child #9 completed.
    '       Attached child #0 completed.
    '       Attached child #8 completed.
    '       Attached child #1 completed.
    '       Attached child #7 completed.
    '       Attached child #2 completed.
    '       Attached child #6 completed.
    '       Attached child #3 completed.
    '       Attached child #5 completed.
    '       Attached child #4 completed.
    '       Parent task completed.
End Namespace

親タスクでは、TaskCreationOptions.DenyChildAttach オプションを使用して、他のタスクが親タスクにアタッチされないようにすることができます。 詳細については、「アタッチされた子タスクとデタッチされた子タスク」を参照してください。

タスクの完了を待機しています

System.Threading.Tasks.Task 型と System.Threading.Tasks.Task<TResult> 型には、タスクの完了を待機できる Task.Wait メソッドのオーバーロードがいくつか用意されています。 さらに、静的 Task.WaitAll メソッドと Task.WaitAny メソッドのオーバーロードを使用すると、タスクの配列の一部またはすべてが完了するまで待機できます。

通常は、次のいずれかの理由でタスクを待機します。

  • メイン スレッドは、タスクによって計算された最終的な結果に依存します。

  • タスクからスローされる可能性のある例外を処理する必要がある。

  • すべてのタスクの実行が完了する前に、アプリケーションが終了することがあります。 たとえば、コンソール アプリケーションは、Main 内のすべての同期コード (アプリケーション エントリ ポイント) が実行された後に終了します。

次の例は、例外処理を伴わない基本的なパターンを示しています。

Task[] tasks = new Task[3]
{
    Task.Factory.StartNew(() => MethodA()),
    Task.Factory.StartNew(() => MethodB()),
    Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);

// Continue on this thread...
Dim tasks() =
{
    Task.Factory.StartNew(Sub() MethodA()),
    Task.Factory.StartNew(Sub() MethodB()),
    Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.
Task.WaitAll(tasks)

' Continue on this thread...

例外処理を示す例については、「例外処理 参照してください。

一部のオーバーロードではタイムアウトを指定でき、他のオーバーロードは入力パラメーターとして追加の CancellationToken を受け取り、待機自体をプログラムまたはユーザー入力に応答して取り消すことができます。

タスクを待機すると、TaskCreationOptions.AttachedToParentオプションを使用して作成されたそのタスクのすべての子タスクも自然に待機されます。 Task.Wait は、タスクが既に完了している場合は直ちに返されます。 Task.Wait メソッドは、タスクの完了後に Task.Wait メソッドが呼び出された場合でも、タスクによって発生した例外をスローします。

タスクを構成する

Task クラスと Task<TResult> クラスには、複数のタスクを作成するのに役立ついくつかのメソッドが用意されています。 これらのメソッドは、一般的なパターンを実装し、C#、Visual Basic、F# によって提供される非同期言語機能をより適切に使用します。 このセクションでは、WhenAllWhenAnyDelay、および FromResult メソッドについて説明します。

Task.WhenAll

Task.WhenAll メソッドは、複数の Task または Task<TResult> オブジェクトが完了するまで非同期的に待機します。 提供されるオーバーロード バージョンにより、不均一なタスクのセットを待機することができます。 たとえば、複数の Task オブジェクトと Task<TResult> オブジェクトが 1 つのメソッド呼び出しから完了するまで待機できます。

Task.WhenAny

Task.WhenAny メソッドは、複数の Task または Task<TResult> オブジェクトのいずれかが完了するまで非同期的に待機します。 Task.WhenAll メソッドと同様に、このメソッドは、一様でないタスク セットを待機できるオーバーロードされたバージョンを提供します。 WhenAny メソッドは、次のシナリオで特に役立ちます。

  • 冗長操作: さまざまな方法で実行できるアルゴリズムまたは操作を検討してください。 WhenAny メソッドを使用して、最初に完了した操作を選択してから、残りの操作を取り消すことができます。

  • インターリーブ操作: 完了する必要がある複数の操作を開始し、WhenAny メソッドを使用して、各操作の終了時に結果を処理できます。 1 つの操作が完了したら、1 つ以上のタスクを開始できます。

  • 調整操作: WhenAny メソッドを使用して、同時実行操作の数を制限することで、前のシナリオを拡張できます。

  • 期限切れの操作: WhenAny メソッドを使用して、1 つ以上のタスクと、特定の時間が経過した後に終了するタスク (Delay メソッドによって返されるタスクなど) を選択できます。 Delay メソッドについては、次のセクションで説明します。

Task.Delay (タスクの遅延)

Task.Delay メソッドは、指定した時刻より後に終了する Task オブジェクトを生成します。 このメソッドを使用すると、データをポーリングするループを構築したり、タイムアウトを指定したり、ユーザー入力の処理を遅らせたりすることができます。

Task(T).FromResult

Task.FromResult メソッドを使用すると、事前に計算された結果を保持する Task<TResult> オブジェクトを作成できます。 このメソッドは、Task<TResult> オブジェクトを返す非同期操作を実行し、その Task<TResult> オブジェクトの結果が既に計算されている場合に便利です。 FromResult を使用してキャッシュに保持されている非同期ダウンロード操作の結果を取得する例については、「方法: 事前計算済みタスクを作成する」を参照してください。

タスクでの例外の処理

タスクが 1 つ以上の例外をスローすると、例外は AggregateException 例外でラップされます。 この例外は、タスクと結合するスレッドに反映されます。 通常は、タスクが完了するのを待機しているスレッドか、Result プロパティにアクセスするスレッドです。 この動作により、.NET Framework ポリシーが適用されます。既定では、ハンドルされない例外はすべてプロセスを終了する必要があります。 呼び出し元のコードは、try/catch ブロックで次のいずれかを使用して例外を処理できます。

結合スレッドは、タスクがガベージ コレクションされる前に Exception プロパティにアクセスして例外を処理することもできます。 このプロパティにアクセスすると、未処理の例外が、オブジェクトの終了時にプロセスを終了する例外伝達動作をトリガーすることを防ぐことができます。

例外とタスクの詳細については、「例外処理」を参照してください。

タスクの取り消し

Task クラスは協調キャンセルをサポートし、.NET Framework 4 で導入された System.Threading.CancellationTokenSource クラスと System.Threading.CancellationToken クラスと完全に統合されています。 System.Threading.Tasks.Task クラスのコンストラクターの多くは、CancellationToken オブジェクトを入力パラメーターとして受け取ります。 StartNew および Run オーバーロードの多くに、CancellationToken パラメーターも含まれています。

トークンを作成し、後で CancellationTokenSource クラスを使用してキャンセル要求を発行できます。 トークンを引数として Task に渡し、ユーザー デリゲート内の同じトークンを参照します。これは、キャンセル要求に応答する処理を行います。

詳細については、「タスクのキャンセル」および「方法: タスクとその子を取り消す」を参照してください。

TaskFactory クラス

TaskFactory クラスは、タスクと継続タスクを作成および開始するための一般的なパターンをカプセル化する静的メソッドを提供します。

既定の TaskFactory には、Task クラスまたは Task<TResult> クラスの静的プロパティとしてアクセスできます。 TaskFactory を直接インスタンス化し、CancellationTokenTaskCreationOptions オプション、TaskContinuationOptions オプション、または TaskSchedulerを含むさまざまなオプションを指定することもできます。 タスク ファクトリの作成時に指定されたオプションは、TaskCreationOptions 列挙を使用して Task が作成されない限り、作成するすべてのタスクに適用されます。その場合、タスクのオプションはタスク ファクトリのオプションをオーバーライドします。

デリゲートのないタスク

場合によっては、Task を使用して、ユーザー デリゲートではなく外部コンポーネントによって実行される非同期操作をカプセル化することが必要になる場合があります。 操作が非同期プログラミング モデルの開始/終了パターンに基づいている場合は、FromAsync メソッドを使用できます。 そうでない場合は、TaskCompletionSource<TResult> オブジェクトを使用して操作をタスクにラップし、プログラミング Task 利点の一部を得ることができます。 たとえば、例外の伝達と継続のサポートなどです。 詳細については、TaskCompletionSource<TResult>を参照してください。

カスタム スケジューラ

ほとんどのアプリケーションまたはライブラリ開発者は、タスクが実行されるプロセッサ、タスクの作業を他のタスクと同期する方法、またはタスクが System.Threading.ThreadPoolでどのようにスケジュールされているかは気にしません。 必要なのは、ホスト コンピューター上で可能な限り効率的に実行することだけです。 スケジュールの詳細をより細かく制御する必要がある場合、TPL を使用すると、既定のタスク スケジューラで一部の設定を構成したり、カスタム スケジューラを指定したりできます。 詳細については、TaskSchedulerを参照してください。

TPL には、並列および順次シナリオで役立つ新しいパブリック型がいくつか用意されています。 これには、System.Collections.Concurrent 名前空間内のいくつかのスレッド セーフで高速でスケーラブルなコレクション クラスと、いくつかの新しい同期の種類が含まれます。 たとえば、特定の種類のワークロードの先行タスクよりも効率的な System.Threading.SemaphoreSystem.Threading.ManualResetEventSlimです。 .NET Framework 4 のその他の新しい型 (System.Threading.BarrierSystem.Threading.SpinLockなど) は、以前のリリースでは使用できなかった機能を提供します。 詳細については、「並列プログラミング データ構造」を参照してください。

カスタム タスクの種類

System.Threading.Tasks.Task または System.Threading.Tasks.Task<TResult>から継承しないことをお勧めします。 代わりに、AsyncState プロパティを使用して、追加のデータまたは状態を Task または Task<TResult> オブジェクトに関連付けすることをお勧めします。 拡張メソッドを使用して、Task クラスと Task<TResult> クラスの機能を拡張することもできます。 拡張メソッドの詳細については、「拡張メソッド」および「拡張メソッド」を参照してください。

Task または Task<TResult>から継承する必要がある場合は、Run クラス、System.Threading.Tasks.TaskFactoryクラス、System.Threading.Tasks.TaskFactory<TResult>クラス、または System.Threading.Tasks.TaskCompletionSource<TResult> クラスを使用してカスタム タスクの種類のインスタンスを作成することはできません。 これらのクラスは Task オブジェクトと Task<TResult> オブジェクトのみを作成するため、使用できません。 さらに、TaskTask<TResult>TaskFactory、および TaskFactory<TResult> によって提供されるタスク継続メカニズムを使用して、カスタム タスクの種類のインスタンスを作成することはできません。 これらのクラスは、Task オブジェクトと Task<TResult> オブジェクトのみを作成するため、使用できません。

タイトル 説明
継続タスク を使用してタスクを連結する 継続のしくみについて説明します。
アタッチされた子タスクとデタッチされた子タスク 添付された子タスクと分離された子タスクの違いについて説明します。
タスクの取り消し Task オブジェクトに組み込まれている取り消しサポートについて説明します。
例外処理 同時実行スレッドの例外を処理する方法について説明します。
方法: Parallel.Invoke を使用して並列操作を実行する Invokeの使用方法について説明します。
方法: タスク から値を返す タスクから値を返す方法について説明します。
方法: タスクとその子を取り消す タスクを取り消す方法について説明します。
方法: 事前計算済みタスク を作成する Task.FromResult メソッドを使用して、キャッシュに保持されている非同期ダウンロード操作の結果を取得する方法について説明します。
方法: 並列タスク を使用してバイナリ ツリーを走査する タスクを使用してバイナリ ツリーを走査する方法について説明します。
方法: 入れ子になったタスクを解除する Unwrap 拡張メソッドを使用する方法を示します。
データの並列化 ForForEach を使用して、データに対して並列ループを作成する方法について説明します。
並列プログラミング .NET Framework 並列プログラミングの最上位ノード。

関連項目