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

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

  • システム リソースをより効率的かつスケーラブルに利用する。

    背後では、タスクは ThreadPool へのキューとして配置されます。これはスレッド数を判別および調整するアルゴリズムを使用して強化されています。 これらのアルゴリズムは、スループットを最大化するための負荷分散を提供します。 これによりタスクが比較的軽量化されるため、多数作成することで粒度の高い並列化を実現できます。

  • スレッドまたは作業項目より、プログラムによる制御を詳細に行うことができる。

    タスクおよびタスクを中心に構築されたフレームワークでは、待機、キャンセル、継続、信頼性の高い例外処理、詳細なステータス、カスタムのスケジュール設定などをサポートする豊富な API が用意されています。

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

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

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

Note

ここでは、ラムダ式を使用して 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 クラスによって表されます。 値を返すタスクは、System.Threading.Tasks.Task<TResult> から継承された Task クラスで表されます。 タスク オブジェクトはインフラストラクチャの詳細を処理し、タスクの有効期間内に呼び出し元のスレッドからアクセスできるメソッドとプロパティを提供します。 たとえば、タスクの 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 メソッドを使用して、一度の操作でタスクを作成および開始することもできます。 タスクを管理するために、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 メソッドを使用して、一度の操作でタスクを作成および開始することもできます。 次の例に示すように、次の場合にこのメソッドを使用できます。

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

  • 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

Task および Task<TResult> はそれぞれ、Factory の既定のインスタンスを返す、静的な TaskFactory プロパティを公開するので、メソッドを Task.Factory.StartNew() として呼び出すことができます。 また、次の例のタスクは 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

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

タスクの作成オプション

タスクを作成するほとんどの API には、TaskCreationOptions パラメーターを受け入れるオーバーロードが用意されています。 これらのオプションを 1 つ以上指定すると、タスク スケジューラにスレッド プール上のタスクをスケジュールする方法を指定できます。 ビットごとの OR 演算を使用して、オプションを組み合わせることもできます。

次の例は、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()

タスク、スレッド、およびカルチャ

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

システム カルチャは、スレッドの既定のカルチャと UI カルチャを定義します。 ただし、CultureInfo.DefaultThreadCurrentCultureCultureInfo.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 €

Note

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

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

タスクの継続の作成

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

次の例では、getData タスクは TaskFactory.StartNew<TResult>(Func<TResult>) メソッドの呼び出しによって開始されます。 processData タスクは getData が終了したときに自動的に開始され、displayDataprocessData が終了したときに開始されます。 getData は、processData タスクの getData プロパティを使用して Task<TResult>.Result タスクがアクセス可能な、整数の配列を生成します。 processData タスクはその配列を処理し、Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>) メソッドに渡されるラムダ式の戻り値の型から推論される型を持つ結果を返します。 displayData タスクは、processData が終了したときに自動的に実行され、Tuple<T1,T2,T3> ラムダ式が返した processData オブジェクトは、displayData タスクの processData プロパティを使用して、Task<TResult>.Result タスクからアクセス可能です。 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 メソッドがタスクの完了後に呼び出された場合でも、タスクで発生したすべての例外をスローします。

タスクを構成する

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

Task.WhenAll

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

Task.WhenAny

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

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

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

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

  • 期限切れの操作: WhenAny メソッドを使うと、1 つ以上のタスクと特定の時間後に終了する 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 などです。 タスク ファクトリを作成するときに指定されるオプションは、タスク ファクトリで作成したすべてのタスクに適用されます。ただし、TaskTaskCreationOptions 列挙型を使用して作成された場合は例外で、タスクのオプションによってタスク ファクトリのオプションがオーバーライドされます。

デリゲートなしのタスク

Task を使用して、ユーザー デリゲートではなく外部コンポーネントによって実行される非同期操作をカプセル化する場合があります。 操作が非同期プログラミング モデルの Begin/End パターンに基づいている場合、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> クラスの機能を拡張することもできます。 拡張メソッドの詳細については、拡張メソッド (C# プログラミングガイド)拡張メソッド (Visual Basic) に関する記事を参照してください。

Task または Task<TResult> から継承する必要がある場合、RunSystem.Threading.Tasks.TaskFactorySystem.Threading.Tasks.TaskFactory<TResult>System.Threading.Tasks.TaskCompletionSource<TResult> の各クラスを使用して、カスタムのタスクの型のインスタンスを作成することはできません。 これらのクラスは TaskTask<TResult> のオブジェクトしか作成しないため、使用できません。 また、TaskTask<TResult>TaskFactory、および TaskFactory<TResult> で提供されるタスク継続機構を使用して、カスタムのタスクの型のインスタンスを作成することもできません。 これらのクラスも TaskTask<TResult> のオブジェクトしか作成しないため、使用できません。

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

関連項目