Megosztás a következőn keresztül:


Feladatalapú aszinkron programozás

A tevékenység párhuzamos kódtára (TPL) egy tevékenységkoncepcióján alapul, amely egy aszinkron műveletet jelöl. Bizonyos módokon egy tevékenység hasonlít egy szálra vagy ThreadPool munkaelemre, de magasabb absztrakciós szinten. A tevékenység-párhuzamosság kifejezés egy vagy több egyidejűleg futó független tevékenységre utal. A feladatok két elsődleges előnyt biztosítanak:

  • A rendszererőforrások hatékonyabb és skálázhatóbb használata.

    A színfalak mögött a feladatok sorba állnak a ThreadPool-nál, amelyet olyan algoritmusokkal fejlesztettek tovább, amelyek meghatározzák és igazodnak a szálak számához. Ezek az algoritmusok terheléselosztást biztosítanak az átviteli sebesség maximalizálása érdekében. Ez a folyamat viszonylag könnyűvé teszi a feladatokat, és sok ilyen feladatot hozhat létre a finom szemcséjű párhuzamosság érdekében.

  • Nagyobb programozott vezérlés, mint ami egy szál vagy munkaelem esetén lehetséges.

    A feladatok és az ezek köré épített keretrendszer számos olyan API-t biztosítanak, amelyek támogatják a várakozást, a lemondást, a folytatásokat, a robusztus kivételkezelést, a részletes állapotot, az egyéni ütemezést és egyebeket.

Mindkét okból a TPL az elsődleges API a többszálas, aszinkron és párhuzamos kód írásához a .NET-ben.

Tevékenységek implicit létrehozása és futtatása

A Parallel.Invoke metódus kényelmes módot kínál tetszőleges számú utasítás egyidejű futtatására. Csak adjon át egy Action meghatalmazottat minden egyes munkaelemhez. A meghatalmazottak létrehozásának legegyszerűbb módja a lambdakifejezések használata. A lambda kifejezés meghívhat egy elnevezett metódust, vagy megadhatja a kódot beágyazottan. Az alábbi példa egy egyszerű Invoke hívást mutat be, amely két egyidejűleg futó feladatot hoz létre és indít el. Az első feladatot egy lambda kifejezés jelöli, amely egy DoSomeWorknevű metódust hív meg, a második feladatot pedig egy lambda kifejezés képviseli, amely egy DoSomeOtherWorknevű metódust hív meg.

Jegyzet

Ez a dokumentáció lambdakifejezéseket használ a meghatalmazottak definiálásához a TPL-ben. Ha nem ismeri a lambdakifejezéseket a C# vagy a Visual Basic alkalmazásban, tekintse meg Lambda-kifejezéseket a PLINQ-ban és a TPL-.

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

Jegyzet

Az Task által a színfalak mögött létrehozott Invoke példányok száma nem feltétlenül egyenlő a megadott delegáltak számával. A TPL különböző optimalizálásokat alkalmazhat, különösen nagy számú meghatalmazott esetén.

További információért lásd: Hogyan: A Parallel.Invoke használata párhuzamos műveletek végrehajtására.

A tevékenység végrehajtásának nagyobb mértékű szabályozásához vagy a tevékenységből származó érték visszaadásához explicitebb módon kell dolgoznia Task objektumokkal.

Feladatok explicit létrehozása és futtatása

Az System.Threading.Tasks.Task osztály képviseli azokat a feladatokat, amelyek nem adnak vissza értéket. Az értéket visszaadó feladatot a System.Threading.Tasks.Task<TResult> osztály képviseli, amely a Task-ből örököl. A tevékenységobjektum kezeli az infrastruktúra részleteit, és olyan metódusokat és tulajdonságokat biztosít, amelyek a hívó szálról érhetők el a tevékenység teljes élettartama során. Egy tevékenység Status tulajdonságához bármikor hozzáférhet, így megállapíthatja, hogy a tevékenység elindult-e, futott-e a befejezésig, megszakította-e, vagy kivételt jelzett-e. Az állapotot egy TaskStatus enumerálás jelöli.

Amikor létrehoz egy feladatot, egy felhasználói meghatalmazottat ad neki, aki beágyazza a feladat által végrehajtandó kódot. A delegált kifejezhető elnevezett delegáltként, névtelen metódusként vagy lambdakifejezésként. A Lambda-kifejezések tartalmazhatnak egy elnevezett metódusra irányuló hívást, ahogy az az alábbi példában is látható. A példa a Task.Wait metódus hívását is tartalmazza, amely biztosítja, hogy a feladat a konzol módú alkalmazás befejeződése előtt befejeződjön.

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 '{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

A Task.Run metódusokkal is létrehozhat és elindíthat feladatokat egy műveletben. A feladat kezeléséhez a Run metódusok az alapértelmezett feladatütemezőt használják, függetlenül attól, hogy melyik tevékenységütemező van társítva az aktuális szálhoz. A Run metódusok a tevékenységek létrehozásának és elindításának előnyben részesített módja, amikor nincs szükség nagyobb kontrollra a tevékenység létrehozása és ütemezése felett.

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 '{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

A TaskFactory.StartNew metódussal is létrehozhat és elindíthat egy feladatot egy műveletben. Az alábbi példában látható módon ezt a módszert akkor használhatja, ha:

  • A létrehozást és az ütemezést nem kell különválasztani, és további feladatlétrehozási lehetőségekre vagy adott ütemező használatára van szükség.

  • További állapotot kell átadnia a feladatnak, amelyet a Task.AsyncState tulajdonságán keresztül lekérhet.

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 és Task<TResult> mindegyik egy statikus Factory tulajdonságot tesz elérhetővé, amely egy alapértelmezett TaskFactory-példányt ad vissza, így a metódust Task.Factory.StartNew()néven hívhatja meg. Az alábbi példában, mivel a tevékenységek System.Threading.Tasks.Task<TResult>típusúak, mindegyikhez tartozik egy nyilvános Task<TResult>.Result tulajdonság, amely tartalmazza a számítás eredményét. A feladatok aszinkron módon futnak, és bármilyen sorrendben befejeződhetnek. Ha a Result tulajdonság a számítás befejezése előtt érhető el, a tulajdonság blokkolja a hívó szálat, amíg az érték el nem érhető.

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($"{sum:N1}");
   }

   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

További információ: Hogyan lehet visszaadni egy értéket egy feladatból.

Ha lambda kifejezéssel hoz létre meghatalmazottat, hozzáférhet a forráskód ezen pontján látható összes változóhoz. Bizonyos esetekben azonban, különösen a hurkokban, a lambda nem a várt módon rögzíti a változót. Csak a változó hivatkozását rögzíti, nem az értéket, mivel az egyes iterációk után mutál. Az alábbi példa a problémát szemlélteti. Egy ciklusszámlálót ad át egy lambda függvénynek, amely létrehoz egy CustomData objektumot, és a ciklusszámlálót használja az objektum azonosítójaként. Ahogy a példa kimenete is mutatja, minden CustomData objektum azonos azonosítóval rendelkezik.

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 #{data.Name} created at {data.CreationTime} on thread #{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

Az egyes iterációk értékeit úgy érheti el, hogy a konstruktoron keresztül egy állapotobjektumot ad egy feladatnak. Az alábbi példa a ciklusszámlálóval módosítja az előző példát a CustomData objektum létrehozásakor, amelyet a rendszer átad a lambda kifejezésnek. Ahogy a példa kimenete is mutatja, mindegyik CustomData objektum egyedi azonosítót kapott a hurokszámláló értéke alapján, amikor az objektum példányosítva lett.

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 #{data.Name} created at {data.CreationTime} on thread #{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

Ezt az állapotot a rendszer argumentumként továbbítja a tevékenységmegbízottnak, és a tevékenységobjektumból a Task.AsyncState tulajdonság használatával érhető el. Az alábbi példa az előző példában szereplő változat. A AsyncState tulajdonság használatával jeleníti meg a lambda kifejezésnek átadott CustomData objektumok adatait.

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

Tevékenységazonosító

Minden tevékenység kap egy egész számazonosítót, amely egyedileg azonosítja azt egy alkalmazástartományban, és az Task.Id tulajdonság használatával érhető el. Hasznos az azonosító a Visual Studio hibakeresőjének Párhuzamos halmok és Feladatok ablakai megtekintéséhez. Az azonosító lazán jön létre, ami azt jelenti, hogy csak a kérés után jön létre. Ezért előfordulhat, hogy egy tevékenység minden futtatásakor más azonosítóval rendelkezik. A feladatazonosítók hibakeresőben való megtekintéséről további információt a A Tevékenységek ablak és a párhuzamos veremablak használatacímű témakörben talál.

Feladatlétrehozás beállításai

A feladatokat létrehozó API-k többsége túlterheléseket biztosít, amelyek elfogadják a TaskCreationOptions paramétert. Egy vagy több beállítás megadásával megadhatja a feladatütemezőnek, hogyan ütemezze a feladatot a szálkészleten. A beállításokat bitenkénti VAGY művelettel kombinálhatja.

Az alábbi példa egy olyan feladatot mutat be, amely LongRunning és PreferFairness beállításokkal rendelkezik:

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

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

Feladatok, szálak és kultúra

Minden szál rendelkezik egy társított kultúrával és felhasználói felületi kultúrával, amelyet a Thread.CurrentCulture és Thread.CurrentUICulture tulajdonságok határoznak meg. A szál kulturális beállításai olyan műveletekben használatosak, mint a formázás, az elemzés, a rendezés és a szövegösszehasonlító műveletek. A szál UI kultúráját használják az erőforrás-kereséshez.

A rendszerkultúra határozza meg egy szál alapértelmezett kulturális és felhasználói felületi kultúráját. A CultureInfo.DefaultThreadCurrentCulture és CultureInfo.DefaultThreadCurrentUICulture tulajdonságok használatával azonban megadhat egy alapértelmezett kultúrát az alkalmazástartomány összes szálához. Ha explicit módon állítja be egy szál kulturális beállítását, és elindít egy új szálat, az új szál nem örökli a kiinduló szál kulturális beállítását; ehelyett a kulturális beállítás az alapértelmezett rendszerkultúra. A feladatalapú programozásban azonban a tevékenységek a hívószál kultúráját használják, még akkor is, ha a tevékenység aszinkron módon fut egy másik szálon.

Az alábbi példa egy egyszerű ábrát mutat be. Az alkalmazás jelenlegi kultúráját francia (Franciaország) nyelvre módosítja. Ha a francia (Franciaország) az aktuális kultúra, akkor angol (Egyesült Államok) nyelvre változik. Ezután meghív egy formatDelegate nevű meghatalmazottat, amely az új kultúrában pénznemértékként formázott számokat ad vissza. Függetlenül attól, hogy a delegátust egy feladat szinkron vagy aszinkron módon hívja meg, a feladat a hívószál kultúráját használja.

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 {Thread.CurrentThread.ManagedThreadId}");
       // Make the current culture different from the system culture.
       Console.WriteLine($"The current culture is {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 {CultureInfo.CurrentCulture.Name}.\n");

       // 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 €

Jegyzet

A .NET-keretrendszer 4.6-nál korábbi verzióiban a feladat kultúráját annak a szálnak a kultúrája határozza meg, amelyen fut, nem pedig a hívószál kultúrája. Az aszinkron feladatok esetében a tevékenység által használt kultúra eltérhet a hívószál kultúrájától.

Az aszinkron tevékenységekről és kultúráról a CultureInfo cikk "Kultúra és aszinkron tevékenységalapú műveletek" című szakaszában talál további információt.

Tevékenység folytatásainak létrehozása

A Task.ContinueWith és Task<TResult>.ContinueWith metódusokkal megadhat egy tevékenységet, amely akkor indul el, amikor a előzményfeladat befejeződik. A folytatási tevékenység delegáltja az előzménytevékenységre mutató hivatkozást kap, hogy megvizsgálhassa az előzménytevékenység állapotát. A Task<TResult>.Result tulajdonság értékének beolvasásával pedig az előzmény kimenetét használhatja bemenetként a folytatáshoz.

Az alábbi példában a getData feladatot a TaskFactory.StartNew<TResult>(Func<TResult>) metódus hívása indítja el. A processData tevékenység automatikusan elindul, amikor getData befejeződik, és displayDataprocessData befejeződésekor indul el. getData egy egész számtömböt hoz létre, amely a processData tevékenység getData tulajdonságán keresztül érhető el a Task<TResult>.Result tevékenység számára. A processData feladat feldolgozza a tömböt, és olyan eredményt ad vissza, amelynek típusa a Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>) metódusnak átadott lambda kifejezés visszatérési típusából származik. A displayData feladat automatikusan végrehajtja processData befejeződésekor, és a Tuple<T1,T2,T3> lambda kifejezés által visszaadott processData objektum a displayData tevékenység processData tulajdonságán keresztül érhető el a Task<TResult>.Result tevékenység számára. A displayData tevékenység a processData tevékenység eredményét veszi át. Olyan eredményt hoz létre, amelynek típusa hasonló módon következtethető ki, és amely a program számára elérhetővé válik a Result tulajdonságban.

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

Mivel a Task.ContinueWith egy példánymetódus, a metódushívásokat összefűzheti egymással, ahelyett, hogy minden egyes előzményfeladathoz külön Task<TResult> objektumot példányosítson. Az alábbi példa funkcionálisan megegyezik az előző példával, azzal a kivételével, hogy a Task.ContinueWith metódushoz intézett hívásokat összekapcsolja. A metódushívások lánca által visszaadott Task<TResult> objektum a végső folytatási feladat.

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

A ContinueWhenAll és ContinueWhenAny módszerekkel több feladatból is folytathatja a munkát.

További információért lásd: Feladatok láncolása folytatólagos tevékenységek használatával.

Leválasztott gyermekfeladatok létrehozása

Ha egy tevékenységben futó felhasználói kód létrehoz egy új feladatot, és nem adja meg a AttachedToParent beállítást, az új tevékenység semmilyen különleges módon nem szinkronizálva lesz a szülőtevékenységgel. Ezt a nem szinkronizált feladatot leválasztott beágyazott feladatnak nevezzük, vagy leválasztott gyermekfeladatnak. Az alábbi példa egy olyan feladatot mutat be, amely egy leválasztott gyermekfeladatot hoz létre:

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.

Jegyzet

A szülőfeladat nem várja meg, hogy a leválasztott gyermekfeladat végrehajtódjon.

Gyermekfeladatok létrehozása

Amikor egy feladatban futó felhasználói kód létrehoz egy új feladatot a AttachedToParent beállítással, az új feladatot a szülőfeladat csatolt gyermekfeladataként ismerjük. A AttachedToParent lehetőséggel kifejezheti a strukturált tevékenységek párhuzamosságát, mivel a szülőtevékenység implicit módon megvárja, amíg az összes csatolt gyermektevékenység befejeződik. Az alábbi példa egy szülőfeladatot mutat be, amely 10 csatolt gyermekfeladatot hoz létre. A példa meghívja a Task.Wait metódust, hogy várja meg a szülőfeladat befejezését. Nem kell explicit módon várnia, amíg a csatolt gyermekfeladatok befejeződnek.

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 #{x} completed.");
                                               },
                                               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

A szülőtevékenységek a TaskCreationOptions.DenyChildAttach beállítással megakadályozhatják, hogy más tevékenységek kapcsolódjanak a szülőtevékenységhez. További információ: Csatolt és leválasztott gyermekfeladatok.

Várakozás a tevékenységek befejezésére

A System.Threading.Tasks.Task és a System.Threading.Tasks.Task<TResult> típus számos túlterhelést biztosít a Task.Wait metódusoknak, amelyek lehetővé teszik a tevékenységek befejezésére való várakozást. Emellett a statikus Task.WaitAll és Task.WaitAny metódusok túlterhelése lehetővé teszi, hogy megvárhatja a feladatok bármelyikének vagy összesének befejezését.

Általában az alábbi okok valamelyike miatt várna egy feladatra:

  • A fő szál a tevékenység által kiszámított végső eredménytől függ.

  • Önnek kezelnie kell a feladatból esetlegesen dobott kivételeket.

  • Az alkalmazás az összes tevékenység végrehajtása előtt leállhat. A konzolalkalmazások például az Main (az alkalmazás belépési pontja) összes szinkron kódjának végrehajtása után leállnak.

Az alábbi példa azt az alapszintű mintát mutatja be, amely nem jár kivételkezeléssel:

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...

A kivételkezelést bemutató példát Kivételkezeléscímű témakörben talál.

Bizonyos túlterhelések lehetővé teszik az időtúllépés megadását, míg mások egy további CancellationToken beviteli paramétert vesznek igénybe, hogy a várakozás maga programozott módon vagy a felhasználói bemenetre reagálva megszakítható legyen.

Amikor egy feladatra vár, implicit módon megvárja az összes gyermekfeladatot, amelyeket a TaskCreationOptions.AttachedToParent beállítással hoztak létre. Task.Wait azonnal visszatér, ha a tevékenység már befejeződött. A Task.Wait metódusok a tevékenység által kiváltott kivételeket akkor is kivetik, ha a Task.Wait metódust a tevékenység befejezése után hívták meg.

Feladatok összeállítása

A Task és Task<TResult> osztályok számos módszert kínálnak több feladat összeállításához. Ezek a módszerek közös mintákat alkalmaznak, és jobban kihasználják a C#, a Visual Basic és az F# által biztosított aszinkron nyelvi funkciókat. Ez a szakasz a WhenAll, WhenAny, Delayés FromResult metódusokat ismerteti.

Task.WhenAll

A Task.WhenAll metódus aszinkron módon több Task vagy Task<TResult> objektum befejezésére vár. Túlterheléses verziókat biztosít, amelyek lehetővé teszik, hogy nem egyenletes feladathalmazokra várakozz. Várhatja például, hogy több Task és Task<TResult> objektum befejeződjön egy metódushívásból.

Task.WhenAny

A Task.WhenAny metódus aszinkron módon várakozik, amíg több Task vagy Task<TResult> objektum valamelyike befejeződik. A Task.WhenAll metódushoz hasonlóan ez a módszer túlterhelt verziókat biztosít, amelyek lehetővé teszik a nem egységes feladatkészletek várakozását. A WhenAny módszer különösen hasznos a következő helyzetekben:

  • Redundáns műveletek: Fontolja meg egy algoritmust vagy műveletet, amely számos módon végrehajtható. A WhenAny metódus használatával kiválaszthatja az elsőként befejezett műveletet, majd megszakíthatja a fennmaradó műveleteket.

  • Interleaved műveletek: Több műveletet is elindíthat, amelyek befejeződése szükséges, és a WhenAny metódust használva minden egyes művelet befejezésekor feldolgozhatja az eredményeket. Miután egy művelet befejeződött, elindíthat egy vagy több feladatot.

  • Korlátozott műveletek: A WhenAny metódussal kibővítheti az előző forgatókönyvet az egyidejűleg végrehajtott műveletek számának korlátozásával.

  • Lejárt műveletek: A WhenAny metódussal választhat egy vagy több tevékenység és egy adott időpont után befejezett tevékenység között, például a Delay metódus által visszaadott tevékenységek közül. A Delay metódust a következő szakaszban ismertetjük.

Task.Delay függvény

A Task.Delay metódus létrehoz egy Task objektumot, amely a megadott idő után fejeződik be. Ezzel a módszerrel olyan hurkokat hozhat létre, amelyek lekérdezik az adatokat, időtúllépéseket határoznak meg, késleltetik a felhasználói bemenetek kezelését stb.

Feladat(T).Eredményből

A Task.FromResult metódus használatával létrehozhat egy Task<TResult> objektumot, amely előre kiszámított eredményt tartalmaz. Ez a módszer akkor hasznos, ha olyan aszinkron műveletet hajt végre, amely egy Task<TResult> objektumot ad vissza, és a Task<TResult> objektum eredménye már ki van számítva. Például ha a(z) FromResult-t használja a gyorsítótárban tárolt aszinkron letöltési műveletek eredményeinek lekéréséhez, tekintse meg Előre kiszámított feladatok létrehozása.

Kivételek kezelése a tevékenységekben

Ha egy tevékenység egy vagy több kivételt ad ki, a kivételek AggregateException kivételbe vannak csomagolva. Ezt a kivételt a rendszer a feladathoz csatlakozó szálra továbbítja. Általában az a szál vár a feladat befejezésére, vagy a szál, amely hozzáfér a Result tulajdonsághoz. Ez a viselkedés kikényszeríti a .NET-keretrendszer szabályzatát, hogy alapértelmezés szerint minden kezeletlen kivételnek le kell mondania a folyamatot. A hívókód az alábbi try/catch blokkok bármelyikével kezelheti a kivételeket:

A csatlakozó szál a kivételeket úgy is kezelheti, hogy a feladat szemétgyűjtése előtt hozzáfér a Exception tulajdonsághoz. A tulajdonság elérésével megakadályozhatja, hogy a kezeletlen kivétel kiváltsa azt a kivételpropagálási viselkedést, amely az objektum véglegesítésekor leállítja a folyamatot.

A kivételekről és feladatokról további információt Kivételkezeléscímű témakörben talál.

Tevékenységek megszakítása

A Task osztály támogatja a kooperatív lemondást, és teljes mértékben integrálva van a .NET-keretrendszer 4-ben bevezetett System.Threading.CancellationTokenSource és System.Threading.CancellationToken osztályokkal. A System.Threading.Tasks.Task osztály számos konstruktora egy CancellationToken objektumot vesz fel bemeneti paraméterként. Számos StartNew és Run túlterhelés is tartalmaz egy CancellationToken paramétert.

Később létrehozhatja a jogkivonatot, és benyújthatja a lemondási kérelmet a CancellationTokenSource osztály használatával. Adja át a tokent a Task argumentumként, és hivatkozzon ugyanarra a tokenre a felhasználói delegáltban is, amely elvégzi a lemondási kérelmekre való reagálást.

További információért lásd: Feladat törlése és Útmutató: Feladat és annak alfeladatainak törlése.

A TaskFactory osztály

A TaskFactory osztály olyan statikus metódusokat biztosít, amelyek közös mintákat foglalnak össze a tevékenységek létrehozásához és elindításához, valamint a tevékenységek folytatásához.

Az alapértelmezett TaskFactory a Task osztály vagy Task<TResult> osztály statikus tulajdonságaként érhető el. Közvetlenül is létrehozhat egy TaskFactory, és különböző beállításokat is megadhat, például egy CancellationToken, egy TaskCreationOptions lehetőséget, egy TaskContinuationOptions lehetőséget vagy egy TaskScheduler. A tevékenység-előállító létrehozásakor megadott beállításokat a rendszer az összes létrehozott tevékenységre alkalmazza, kivéve, ha az Task enumerálással TaskCreationOptions jön létre, ebben az esetben a tevékenység beállításai felülbírálják a feladat-előállító beállításait.

Meghatalmazottak nélküli feladatok

Bizonyos esetekben érdemes lehet egy Task-t használnia néhány aszinkron művelet kapszulázására, amit a felhasználói meghatalmazott helyett egy külső összetevő hajt végre. Ha a művelet az aszinkron programozási modell kezdő/záró mintáján alapul, használhatja a FromAsync metódusokat. Ha ez nem így van, a TaskCompletionSource<TResult> objektummal körbefuttathatja a műveletet egy feladatban, és ezáltal kihasználhatja Task programozhatóság előnyeit. Például a kivételek propagálásának és folytatásának támogatása. További információ: TaskCompletionSource<TResult>.

Egyéni ütemezők

A legtöbb alkalmazás- vagy kódtár-fejlesztő nem törődik azzal, hogy a feladat melyik processzoron fut, hogyan szinkronizálja a munkáját más tevékenységekkel, vagy hogyan ütemezi a System.Threading.ThreadPool. Csak a lehető leghatékonyabb végrehajtást követelik meg a gazdaszámítógépen. Ha részletesebb vezérlésre van szüksége az ütemezés részletei felett, a TPL lehetővé teszi bizonyos beállítások konfigurálását az alapértelmezett feladatütemezőn, és akár egyéni ütemezőt is megadhat. További információ: TaskScheduler.

A TPL számos új nyilvános típust használ, amelyek párhuzamos és szekvenciális forgatókönyvekben hasznosak. Ezek közé tartozik a System.Collections.Concurrent névtér több szálbiztos, gyors és méretezhető gyűjteményosztálya, valamint számos új szinkronizálási típus. Például System.Threading.Semaphore és System.Threading.ManualResetEventSlim, amelyek hatékonyabbak, mint az elődjeik bizonyos típusú számítási feladatokhoz. A .NET-keretrendszer 4 egyéb új típusai, például System.Threading.Barrier és System.Threading.SpinLockolyan funkciókat biztosítanak, amelyek a korábbi kiadásokban nem érhetők el. További információ: Párhuzamos programozás adatstruktúrái.

Egyéni tevékenységtípusok

Azt javasoljuk, hogy ne származtasson a System.Threading.Tasks.Task vagy a System.Threading.Tasks.Task<TResult> elemből. Ehelyett azt javasoljuk, hogy a AsyncState tulajdonsággal társítsa a további adatokat vagy állapotokat egy Task vagy Task<TResult> objektumhoz. Bővítménymetelyekkel bővítheti a Task és Task<TResult> osztályok funkcióit. További információ a kiterjesztési módszerekről: Kiterjesztési módszerek és kiterjesztési módszerek.

Ha a Task-t vagy a Task<TResult>-et kell örökölnie, nem használhatja a Run, a System.Threading.Tasks.TaskFactory, a System.Threading.Tasks.TaskFactory<TResult>vagy a System.Threading.Tasks.TaskCompletionSource<TResult> osztályt az egyéni feladattípus példányainak létrehozásához. Nem használhatja őket, mert ezek az osztályok csak Task és Task<TResult> objektumokat hoznak létre. Emellett nem használhatja a Task, Task<TResult>, TaskFactoryés TaskFactory<TResult> által biztosított feladat-folytatási mechanizmusokat az egyéni tevékenységtípus példányainak létrehozásához. Nem használhatja őket, mert ezek az osztályok csak Task és Task<TResult> objektumokat is létrehoznak.

Cím Leírás
Feladatok láncolása folytatási feladatok használatával A folytatások működését ismerteti.
csatolt és leválasztott gyermekfeladatok A csatolt és a leválasztott gyermekfeladatok közötti különbséget ismerteti.
tevékenység lemondási A Task objektumba beépített lemondási támogatást ismerteti.
Kivételkezelés Az egyidejű szálak kivételeinek kezelését ismerteti.
Útmutató: A Parallel.Invoke használata párhuzamos műveletek végrehajtásához A Invokehasználatát ismerteti.
Útmutató: Érték visszaadása tevékenységből Azt ismerteti, hogyan ad vissza értékeket a tevékenységekből.
Hogyan: Feladat és annak alfeladatainak megszüntetése A tevékenységek megszakításának módját ismerteti.
Útmutató: Előre kiszámított feladatok létrehozása Ismerteti, hogyan használható a Task.FromResult metódus a gyorsítótárban tárolt aszinkron letöltési műveletek eredményeinek lekérésére.
Útmutató: Bináris fa átjárása párhuzamos feladatokkal Ez a cikk azt ismerteti, hogyan használhatja a feladatokat egy bináris fa bejárására.
Útmutató: Hogyan kell kibontani egy beágyazott feladatot Bemutatja a Unwrap bővítménymetódus használatát.
adat-párhuzamosság Ismerteti, hogyan használható For és ForEach párhuzamos hurkok létrehozására az adatokon.
párhuzamos programozás A .NET-keretrendszer párhuzamos programozásának legfelső szintű csomópontja.

Lásd még: