TaskScheduler Klasa
Definicja
Ważne
Niektóre informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany przed wydaniem. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Reprezentuje obiekt, który obsługuje zadania podrzędne na niskim poziomie w kolejce do wątków.
public ref class TaskScheduler abstract
public abstract class TaskScheduler
type TaskScheduler = class
Public MustInherit Class TaskScheduler
- Dziedziczenie
-
TaskScheduler
Przykłady
Poniższy przykład tworzy niestandardowy harmonogram zadań, który ogranicza liczbę wątków używanych przez aplikację. Następnie uruchamia dwa zestawy zadań i wyświetla informacje o zadaniu i wątku, na którym zadanie jest wykonywane.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
class Example
{
static void Main()
{
// Create a scheduler that uses two threads.
LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(2);
List<Task> tasks = new List<Task>();
// Create a TaskFactory and pass it our custom scheduler.
TaskFactory factory = new TaskFactory(lcts);
CancellationTokenSource cts = new CancellationTokenSource();
// Use our factory to run a set of tasks.
Object lockObj = new Object();
int outputItem = 0;
for (int tCtr = 0; tCtr <= 4; tCtr++) {
int iteration = tCtr;
Task t = factory.StartNew(() => {
for (int i = 0; i < 1000; i++) {
lock (lockObj) {
Console.Write("{0} in task t-{1} on thread {2} ",
i, iteration, Thread.CurrentThread.ManagedThreadId);
outputItem++;
if (outputItem % 3 == 0)
Console.WriteLine();
}
}
}, cts.Token);
tasks.Add(t);
}
// Use it to run a second set of tasks.
for (int tCtr = 0; tCtr <= 4; tCtr++) {
int iteration = tCtr;
Task t1 = factory.StartNew(() => {
for (int outer = 0; outer <= 10; outer++) {
for (int i = 0x21; i <= 0x7E; i++) {
lock (lockObj) {
Console.Write("'{0}' in task t1-{1} on thread {2} ",
Convert.ToChar(i), iteration, Thread.CurrentThread.ManagedThreadId);
outputItem++;
if (outputItem % 3 == 0)
Console.WriteLine();
}
}
}
}, cts.Token);
tasks.Add(t1);
}
// Wait for the tasks to complete before displaying a completion message.
Task.WaitAll(tasks.ToArray());
cts.Dispose();
Console.WriteLine("\n\nSuccessful completion.");
}
}
// Provides a task scheduler that ensures a maximum concurrency level while
// running on top of the thread pool.
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
// Indicates whether the current thread is processing work items.
[ThreadStatic]
private static bool _currentThreadIsProcessingItems;
// The list of tasks to be executed
private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
// The maximum concurrency level allowed by this scheduler.
private readonly int _maxDegreeOfParallelism;
// Indicates whether the scheduler is currently processing work items.
private int _delegatesQueuedOrRunning = 0;
// Creates a new instance with the specified degree of parallelism.
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}
// Queues a task to the scheduler.
protected sealed override void QueueTask(Task task)
{
// Add the task to the list of tasks to be processed. If there aren't enough
// delegates currently queued or running to process tasks, schedule another.
lock (_tasks)
{
_tasks.AddLast(task);
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
{
++_delegatesQueuedOrRunning;
NotifyThreadPoolOfPendingWork();
}
}
}
// Inform the ThreadPool that there's work to be executed for this scheduler.
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
// Note that the current thread is now processing work items.
// This is necessary to enable inlining of tasks into this thread.
_currentThreadIsProcessingItems = true;
try
{
// Process all available items in the queue.
while (true)
{
Task item;
lock (_tasks)
{
// When there are no more items to be processed,
// note that we're done processing, and get out.
if (_tasks.Count == 0)
{
--_delegatesQueuedOrRunning;
break;
}
// Get the next item from the queue
item = _tasks.First.Value;
_tasks.RemoveFirst();
}
// Execute the task we pulled out of the queue
base.TryExecuteTask(item);
}
}
// We're done processing items on the current thread
finally { _currentThreadIsProcessingItems = false; }
}, null);
}
// Attempts to execute the specified task on the current thread.
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// If this thread isn't already processing a task, we don't support inlining
if (!_currentThreadIsProcessingItems) return false;
// If the task was previously queued, remove it from the queue
if (taskWasPreviouslyQueued)
// Try to run the task.
if (TryDequeue(task))
return base.TryExecuteTask(task);
else
return false;
else
return base.TryExecuteTask(task);
}
// Attempt to remove a previously scheduled task from the scheduler.
protected sealed override bool TryDequeue(Task task)
{
lock (_tasks) return _tasks.Remove(task);
}
// Gets the maximum concurrency level supported by this scheduler.
public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }
// Gets an enumerable of the tasks currently scheduled on this scheduler.
protected sealed override IEnumerable<Task> GetScheduledTasks()
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_tasks, ref lockTaken);
if (lockTaken) return _tasks;
else throw new NotSupportedException();
}
finally
{
if (lockTaken) Monitor.Exit(_tasks);
}
}
}
// The following is a portion of the output from a single run of the example:
// 'T' in task t1-4 on thread 3 'U' in task t1-4 on thread 3 'V' in task t1-4 on thread 3
// 'W' in task t1-4 on thread 3 'X' in task t1-4 on thread 3 'Y' in task t1-4 on thread 3
// 'Z' in task t1-4 on thread 3 '[' in task t1-4 on thread 3 '\' in task t1-4 on thread 3
// ']' in task t1-4 on thread 3 '^' in task t1-4 on thread 3 '_' in task t1-4 on thread 3
// '`' in task t1-4 on thread 3 'a' in task t1-4 on thread 3 'b' in task t1-4 on thread 3
// 'c' in task t1-4 on thread 3 'd' in task t1-4 on thread 3 'e' in task t1-4 on thread 3
// 'f' in task t1-4 on thread 3 'g' in task t1-4 on thread 3 'h' in task t1-4 on thread 3
// 'i' in task t1-4 on thread 3 'j' in task t1-4 on thread 3 'k' in task t1-4 on thread 3
// 'l' in task t1-4 on thread 3 'm' in task t1-4 on thread 3 'n' in task t1-4 on thread 3
// 'o' in task t1-4 on thread 3 'p' in task t1-4 on thread 3 ']' in task t1-2 on thread 4
// '^' in task t1-2 on thread 4 '_' in task t1-2 on thread 4 '`' in task t1-2 on thread 4
// 'a' in task t1-2 on thread 4 'b' in task t1-2 on thread 4 'c' in task t1-2 on thread 4
// 'd' in task t1-2 on thread 4 'e' in task t1-2 on thread 4 'f' in task t1-2 on thread 4
// 'g' in task t1-2 on thread 4 'h' in task t1-2 on thread 4 'i' in task t1-2 on thread 4
// 'j' in task t1-2 on thread 4 'k' in task t1-2 on thread 4 'l' in task t1-2 on thread 4
// 'm' in task t1-2 on thread 4 'n' in task t1-2 on thread 4 'o' in task t1-2 on thread 4
// 'p' in task t1-2 on thread 4 'q' in task t1-2 on thread 4 'r' in task t1-2 on thread 4
// 's' in task t1-2 on thread 4 't' in task t1-2 on thread 4 'u' in task t1-2 on thread 4
// 'v' in task t1-2 on thread 4 'w' in task t1-2 on thread 4 'x' in task t1-2 on thread 4
// 'y' in task t1-2 on thread 4 'z' in task t1-2 on thread 4 '{' in task t1-2 on thread 4
// '|' in task t1-2 on thread 4 '}' in task t1-2 on thread 4 '~' in task t1-2 on thread 4
// 'q' in task t1-4 on thread 3 'r' in task t1-4 on thread 3 's' in task t1-4 on thread 3
// 't' in task t1-4 on thread 3 'u' in task t1-4 on thread 3 'v' in task t1-4 on thread 3
// 'w' in task t1-4 on thread 3 'x' in task t1-4 on thread 3 'y' in task t1-4 on thread 3
// 'z' in task t1-4 on thread 3 '{' in task t1-4 on thread 3 '|' in task t1-4 on thread 3
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Sub Main()
' Create a scheduler that uses two threads.
Dim lcts As New LimitedConcurrencyLevelTaskScheduler(2)
Dim tasks As New List(Of Task)()
' Create a TaskFactory and pass it our custom scheduler.
Dim factory As New TaskFactory(lcts)
Dim cts As New CancellationTokenSource()
' Use our factory to run a set of tasks.
Dim objLock As New Object()
Dim outputItem As Integer
For tCtr As Integer = 0 To 4
Dim iteration As Integer = tCtr
Dim t As Task = factory.StartNew(Sub()
For i As Integer = 1 To 1000
SyncLock objLock
Console.Write("{0} in task t-{1} on thread {2} ",
i, iteration, Thread.CurrentThread.ManagedThreadId)
outputItem += 1
If outputItem Mod 3 = 0 Then Console.WriteLine()
End SyncLock
Next
End Sub,
cts.Token)
tasks.Add(t)
Next
' Use it to run a second set of tasks.
For tCtr As Integer = 0 To 4
Dim iteration As Integer = tCtr
Dim t1 As Task = factory.StartNew(Sub()
For outer As Integer = 0 To 10
For i As Integer = &h21 To &h7E
SyncLock objLock
Console.Write("'{0}' in task t1-{1} on thread {2} ",
Convert.ToChar(i), iteration, Thread.CurrentThread.ManagedThreadId)
outputItem += 1
If outputItem Mod 3 = 0 Then Console.WriteLine()
End SyncLock
Next
Next
End Sub,
cts.Token)
tasks.Add(t1)
Next
' Wait for the tasks to complete before displaying a completion message.
Task.WaitAll(tasks.ToArray())
cts.Dispose()
Console.WriteLine(vbCrLf + vbCrLf + "Successful completion.")
End Sub
End Module
' Provides a task scheduler that ensures a maximum concurrency level while
' running on top of the thread pool.
Public Class LimitedConcurrencyLevelTaskScheduler : Inherits TaskScheduler
' Indicates whether the current thread is processing work items.
<ThreadStatic()> Private Shared _currentThreadIsProcessingItems As Boolean
' The list of tasks to be executed
Private ReadOnly _tasks As LinkedList(Of Task) = New LinkedList(Of Task)()
'The maximum concurrency level allowed by this scheduler.
Private ReadOnly _maxDegreeOfParallelism As Integer
' Indicates whether the scheduler is currently processing work items.
Private _delegatesQueuedOrRunning As Integer = 0 ' protected by lock(_tasks)
' Creates a new instance with the specified degree of parallelism.
Public Sub New(ByVal maxDegreeOfParallelism As Integer)
If (maxDegreeOfParallelism < 1) Then
Throw New ArgumentOutOfRangeException("maxDegreeOfParallelism")
End If
_maxDegreeOfParallelism = maxDegreeOfParallelism
End Sub
' Queues a task to the scheduler.
Protected Overrides Sub QueueTask(ByVal t As Task)
' Add the task to the list of tasks to be processed. If there aren't enough
' delegates currently queued or running to process tasks, schedule another.
SyncLock (_tasks)
_tasks.AddLast(t)
If (_delegatesQueuedOrRunning < _maxDegreeOfParallelism) Then
_delegatesQueuedOrRunning = _delegatesQueuedOrRunning + 1
NotifyThreadPoolOfPendingWork()
End If
End SyncLock
End Sub
' Inform the ThreadPool that there's work to be executed for this scheduler.
Private Sub NotifyThreadPoolOfPendingWork()
ThreadPool.UnsafeQueueUserWorkItem(Sub()
' Note that the current thread is now processing work items.
' This is necessary to enable inlining of tasks into this thread.
_currentThreadIsProcessingItems = True
Try
' Process all available items in the queue.
While (True)
Dim item As Task
SyncLock (_tasks)
' When there are no more items to be processed,
' note that we're done processing, and get out.
If (_tasks.Count = 0) Then
_delegatesQueuedOrRunning = _delegatesQueuedOrRunning - 1
Exit While
End If
' Get the next item from the queue
item = _tasks.First.Value
_tasks.RemoveFirst()
End SyncLock
' Execute the task we pulled out of the queue
MyBase.TryExecuteTask(item)
End While
' We're done processing items on the current thread
Finally
_currentThreadIsProcessingItems = False
End Try
End Sub,
Nothing)
End Sub
' Attempts to execute the specified task on the current thread.
Protected Overrides Function TryExecuteTaskInline(ByVal t As Task,
ByVal taskWasPreviouslyQueued As Boolean) As Boolean
' If this thread isn't already processing a task, we don't support inlining
If (Not _currentThreadIsProcessingItems) Then
Return False
End If
' If the task was previously queued, remove it from the queue
If (taskWasPreviouslyQueued) Then
' Try to run the task.
If TryDequeue(t) Then
Return MyBase.TryExecuteTask(t)
Else
Return False
End If
Else
Return MyBase.TryExecuteTask(t)
End If
End Function
' Attempt to remove a previously scheduled task from the scheduler.
Protected Overrides Function TryDequeue(ByVal t As Task) As Boolean
SyncLock (_tasks)
Return _tasks.Remove(t)
End SyncLock
End Function
' Gets the maximum concurrency level supported by this scheduler.
Public Overrides ReadOnly Property MaximumConcurrencyLevel As Integer
Get
Return _maxDegreeOfParallelism
End Get
End Property
' Gets an enumerable of the tasks currently scheduled on this scheduler.
Protected Overrides Function GetScheduledTasks() As IEnumerable(Of Task)
Dim lockTaken As Boolean = False
Try
Monitor.TryEnter(_tasks, lockTaken)
If (lockTaken) Then
Return _tasks.ToArray()
Else
Throw New NotSupportedException()
End If
Finally
If (lockTaken) Then
Monitor.Exit(_tasks)
End If
End Try
End Function
End Class
' The following is a portion of the output from a single run of the example:
' 'T' in task t1-4 on thread 3 'U' in task t1-4 on thread 3 'V' in task t1-4 on thread 3
' 'W' in task t1-4 on thread 3 'X' in task t1-4 on thread 3 'Y' in task t1-4 on thread 3
' 'Z' in task t1-4 on thread 3 '[' in task t1-4 on thread 3 '\' in task t1-4 on thread 3
' ']' in task t1-4 on thread 3 '^' in task t1-4 on thread 3 '_' in task t1-4 on thread 3
' '`' in task t1-4 on thread 3 'a' in task t1-4 on thread 3 'b' in task t1-4 on thread 3
' 'c' in task t1-4 on thread 3 'd' in task t1-4 on thread 3 'e' in task t1-4 on thread 3
' 'f' in task t1-4 on thread 3 'g' in task t1-4 on thread 3 'h' in task t1-4 on thread 3
' 'i' in task t1-4 on thread 3 'j' in task t1-4 on thread 3 'k' in task t1-4 on thread 3
' 'l' in task t1-4 on thread 3 'm' in task t1-4 on thread 3 'n' in task t1-4 on thread 3
' 'o' in task t1-4 on thread 3 'p' in task t1-4 on thread 3 ']' in task t1-2 on thread 4
' '^' in task t1-2 on thread 4 '_' in task t1-2 on thread 4 '`' in task t1-2 on thread 4
' 'a' in task t1-2 on thread 4 'b' in task t1-2 on thread 4 'c' in task t1-2 on thread 4
' 'd' in task t1-2 on thread 4 'e' in task t1-2 on thread 4 'f' in task t1-2 on thread 4
' 'g' in task t1-2 on thread 4 'h' in task t1-2 on thread 4 'i' in task t1-2 on thread 4
' 'j' in task t1-2 on thread 4 'k' in task t1-2 on thread 4 'l' in task t1-2 on thread 4
' 'm' in task t1-2 on thread 4 'n' in task t1-2 on thread 4 'o' in task t1-2 on thread 4
' 'p' in task t1-2 on thread 4 'q' in task t1-2 on thread 4 'r' in task t1-2 on thread 4
' 's' in task t1-2 on thread 4 't' in task t1-2 on thread 4 'u' in task t1-2 on thread 4
' 'v' in task t1-2 on thread 4 'w' in task t1-2 on thread 4 'x' in task t1-2 on thread 4
' 'y' in task t1-2 on thread 4 'z' in task t1-2 on thread 4 '{' in task t1-2 on thread 4
' '|' in task t1-2 on thread 4 '}' in task t1-2 on thread 4 '~' in task t1-2 on thread 4
' 'q' in task t1-4 on thread 3 'r' in task t1-4 on thread 3 's' in task t1-4 on thread 3
' 't' in task t1-4 on thread 3 'u' in task t1-4 on thread 3 'v' in task t1-4 on thread 3
' 'w' in task t1-4 on thread 3 'x' in task t1-4 on thread 3 'y' in task t1-4 on thread 3
' 'z' in task t1-4 on thread 3 '{' in task t1-4 on thread 3 '|' in task t1-4 on thread 3
Uwagi
Wystąpienie TaskScheduler klasy reprezentuje harmonogram zadań. Harmonogram zadań gwarantuje, że praca zadania zostanie ostatecznie wykonana.
Domyślny harmonogram zadań jest oparty na puli .NET Framework 4 wątków, która zapewnia kradzież pracy na potrzeby równoważenia obciążenia, wstrzykiwania/wycofywania wątków w celu uzyskania maksymalnej przepływności i ogólnej dobrej wydajności. Powinno to być wystarczające w przypadku większości scenariuszy.
Klasa TaskScheduler służy również jako punkt rozszerzenia dla całej logiki planowania z możliwością dostosowywania. Obejmuje to mechanizmy, takie jak planowanie zadania pod kątem wykonywania i udostępnianie zaplanowanych zadań debugerom. Jeśli potrzebujesz specjalnej funkcjonalności, możesz utworzyć niestandardowy harmonogram i włączyć go dla określonych zadań lub zapytań.
W tym artykule:
Domyślny harmonogram zadań i pula wątków
Kolejka globalna a kolejki lokalne
Kradzież pracy
Długotrwałe zadania
Zwijanie zadań
Określanie kontekstu synchronizacji
Domyślny harmonogram zadań i pula wątków
Domyślny harmonogram dla biblioteki równoległej zadań i PLINQ używa puli wątków platformy .NET reprezentowanej przez klasę ThreadPool do kolejkowania i wykonywania pracy. Pula wątków używa informacji dostarczanych przez Task typ, aby efektywnie obsługiwać precyzyjne równoległości (krótkotrwałe jednostki pracy), które często reprezentują równoległe zadania równoległe i zapytania.
Kolejka globalna a kolejki lokalne
Pula wątków obsługuje globalną kolejkę pracy FIFO (pierwszy na pierwszy na wyjęcie) dla wątków w każdej domenie aplikacji. Za każdym razem, gdy program wywołuje metodę ThreadPool.QueueUserWorkItem (lub ThreadPool.UnsafeQueueUserWorkItem), praca jest umieszczana w tej udostępnionej kolejce i ostatecznie zostanie cokrotna w kolejce do następnego wątku, który stanie się dostępny. Począwszy od .NET Framework 4, ta kolejka używa algorytmu wolnego od blokady, który przypomina klasęConcurrentQueue<T>. Dzięki tej implementacji bez blokady pula wątków spędza mniej czasu, gdy kolejkuje i usuwa elementy robocze. Ta korzyść z wydajności jest dostępna dla wszystkich programów korzystających z puli wątków.
Zadania najwyższego poziomu, które są zadaniami, które nie są tworzone w kontekście innego zadania, są umieszczane w kolejce globalnej tak samo jak w przypadku każdego innego elementu roboczego. Jednak zadania zagnieżdżone lub podrzędne, które są tworzone w kontekście innego zadania, są obsługiwane zupełnie inaczej. Podrzędne lub zagnieżdżone zadanie jest umieszczane w lokalnej kolejce specyficznej dla wątku, w którym jest wykonywane zadanie nadrzędne. Zadanie nadrzędne może być zadaniem najwyższego poziomu lub może być również elementem podrzędnym innego zadania. Gdy ten wątek jest gotowy do większej ilości pracy, najpierw wygląda w kolejce lokalnej. Jeśli elementy robocze oczekują tam, można je szybko uzyskać. Dostęp do kolejek lokalnych jest uzyskiwany w kolejności ostatniego, pierwszego wyjścia (LIFO), aby zachować lokalność pamięci podręcznej i zmniejszyć rywalizację. Aby uzyskać więcej informacji o zadaniach podrzędnych i zadaniach zagnieżdżonych, zobacz Dołączone i odłączone zadania podrzędne.
Korzystanie z kolejek lokalnych nie tylko zmniejsza presję na kolejkę globalną, ale także wykorzystuje lokalność danych. Elementy robocze w kolejce lokalnej często odwołują się do struktur danych, które są fizycznie blisko siebie w pamięci. W takich przypadkach dane są już w pamięci podręcznej po uruchomieniu pierwszego zadania i można uzyskać do nich szybki dostęp. Zarówno równoległa LINQ (PLINQ), jak Parallel i klasa intensywnie używają zagnieżdżonych zadań i zadań podrzędnych, a także znacznie przyspieszają korzystanie z lokalnych kolejek roboczych.
Kradzież pracy
Począwszy od .NET Framework 4, pula wątków zawiera również algorytm kradzieży pracy, aby upewnić się, że żadne wątki nie siedzą bezczynnie, podczas gdy inni nadal pracują w swoich kolejkach. Gdy wątek puli wątków jest gotowy do większej ilości pracy, najpierw patrzy na głowę swojej kolejki lokalnej, a następnie w kolejce globalnej, a następnie w lokalnych kolejkach innych wątków. Jeśli znajdzie element roboczy w lokalnej kolejce innego wątku, najpierw stosuje heurystyki, aby upewnić się, że może działać wydajnie. Jeśli to możliwe, anuluje kolejki elementu roboczego z ogona (w kolejności FIFO). Zmniejsza to rywalizację o każdą kolejkę lokalną i zachowuje lokalność danych. Ta architektura ułatwia wydajniejsze działanie równoważenia obciążenia puli wątków niż w poprzednich wersjach.
Długotrwałe zadania
Możesz jawnie uniemożliwić umieszczanie zadania w kolejce lokalnej. Na przykład możesz wiedzieć, że określony element roboczy będzie uruchamiany przez stosunkowo długi czas i prawdopodobnie zablokuje wszystkie inne elementy robocze w kolejce lokalnej. W takim przypadku można określić System.Threading.Tasks.TaskCreationOptions opcję, która zapewnia wskazówkę dla harmonogramu, że dodatkowy wątek może być wymagany dla zadania, aby nie blokował postępu przekazywania innych wątków lub elementów roboczych w kolejce lokalnej. Korzystając z tej opcji, należy całkowicie unikać puli wątków, w tym kolejek globalnych i lokalnych.
Zwijanie zadań
W niektórych przypadkach, gdy element Task jest czekany, może być wykonywany synchronicznie w wątku wykonującym operację oczekiwania. Zwiększa to wydajność, zapobiegając konieczności użycia dodatkowego wątku i zamiast tego przy użyciu istniejącego wątku, który w przeciwnym razie zostałby zablokowany. Aby zapobiec błędom spowodowanym ponownym uruchomieniem, w kolejce lokalnej odpowiedniego wątku występuje tylko wtedy, gdy element docelowy oczekiwania zostanie znaleziony w kolejce lokalnej odpowiedniego wątku.
Określanie kontekstu synchronizacji
Można użyć TaskScheduler.FromCurrentSynchronizationContext metody , aby określić, że zadanie powinno być zaplanowane do uruchomienia w określonym wątku. Jest to przydatne w strukturach, takich jak Windows Forms i Windows Presentation Foundation, w których dostęp do obiektów interfejsu użytkownika jest często ograniczony do kodu uruchomionego w tym samym wątku, w którym utworzono obiekt interfejsu użytkownika.
W poniższym przykładzie użyto TaskScheduler.FromCurrentSynchronizationContext metody w aplikacji Windows Presentation Foundation (WPF), aby zaplanować zadanie w tym samym wątku, w ramach którego utworzono kontrolkę interfejsu użytkownika. W przykładzie tworzona jest mozaika obrazów wybranych losowo z określonego katalogu. Obiekty WPF są używane do ładowania i zmieniania rozmiaru obrazów. Nieprzetworzone piksele są następnie przekazywane do zadania, które używa For pętli do zapisywania danych pikseli w dużej tablicy jednobajtowej. Synchronizacja nie jest wymagana, ponieważ żadne dwa kafelki nie zajmują tych samych elementów tablicy. Kafelki mogą być również zapisywane w dowolnej kolejności, ponieważ ich pozycja jest obliczana niezależnie od dowolnego innego kafelka. Duża tablica jest następnie przekazywana do zadania uruchamianego w wątku interfejsu użytkownika, w którym dane pikseli są ładowane do kontrolki Obraz.
Przykład przenosi dane z wątku interfejsu użytkownika, modyfikuje je przy użyciu pętli równoległych i Task obiektów, a następnie przekazuje je z powrotem do zadania uruchamianego w wątku interfejsu użytkownika. Takie podejście jest przydatne, gdy trzeba użyć biblioteki równoległej zadań do wykonywania operacji, które nie są obsługiwane przez interfejs API WPF lub nie są wystarczająco szybkie. Innym sposobem na utworzenie mozaiki obrazu w WPF jest użycie System.Windows.Controls.WrapPanel kontrolki i dodanie do niej obrazów. Uchwyty WrapPanel pracy pozycjonowania kafelków. Jednak tę pracę można wykonać tylko w wątku interfejsu użytkownika.
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WPF_CS1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private int fileCount;
int colCount;
int rowCount;
private int tilePixelHeight;
private int tilePixelWidth;
private int largeImagePixelHeight;
private int largeImagePixelWidth;
private int largeImageStride;
PixelFormat format;
BitmapPalette palette = null;
public MainWindow()
{
InitializeComponent();
// For this example, values are hard-coded to a mosaic of 8x8 tiles.
// Each tile is 50 pixels high and 66 pixels wide and 32 bits per pixel.
colCount = 12;
rowCount = 8;
tilePixelHeight = 50;
tilePixelWidth = 66;
largeImagePixelHeight = tilePixelHeight * rowCount;
largeImagePixelWidth = tilePixelWidth * colCount;
largeImageStride = largeImagePixelWidth * (32 / 8);
this.Width = largeImagePixelWidth + 40;
image.Width = largeImagePixelWidth;
image.Height = largeImagePixelHeight;
}
private void button_Click(object sender, RoutedEventArgs e)
{
// For best results use 1024 x 768 jpg files at 32bpp.
string[] files = System.IO.Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures\", "*.jpg");
fileCount = files.Length;
Task<byte[]>[] images = new Task<byte[]>[fileCount];
for (int i = 0; i < fileCount; i++)
{
int x = i;
images[x] = Task.Factory.StartNew(() => LoadImage(files[x]));
}
// When they've all been loaded, tile them into a single byte array.
var tiledImage = Task.Factory.ContinueWhenAll(
images, (i) => TileImages(i));
// We are currently on the UI thread. Save the sync context and pass it to
// the next task so that it can access the UI control "image".
var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
// On the UI thread, put the bytes into a bitmap and
// display it in the Image control.
var t3 = tiledImage.ContinueWith((antecedent) =>
{
// Get System DPI.
Matrix m = PresentationSource.FromVisual(Application.Current.MainWindow)
.CompositionTarget.TransformToDevice;
double dpiX = m.M11;
double dpiY = m.M22;
BitmapSource bms = BitmapSource.Create(largeImagePixelWidth,
largeImagePixelHeight,
dpiX,
dpiY,
format,
palette, //use default palette
antecedent.Result,
largeImageStride);
image.Source = bms;
}, UISyncContext);
}
byte[] LoadImage(string filename)
{
// Use the WPF BitmapImage class to load and
// resize the bitmap. NOTE: Only 32bpp formats are supported correctly.
// Support for additional color formats is left as an exercise
// for the reader. For more information, see documentation for ColorConvertedBitmap.
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(filename);
bitmapImage.DecodePixelHeight = tilePixelHeight;
bitmapImage.DecodePixelWidth = tilePixelWidth;
bitmapImage.EndInit();
format = bitmapImage.Format;
int size = (int)(bitmapImage.Height * bitmapImage.Width);
int stride = (int)bitmapImage.Width * 4;
byte[] dest = new byte[stride * tilePixelHeight];
bitmapImage.CopyPixels(dest, stride, 0);
return dest;
}
int Stride(int pixelWidth, int bitsPerPixel)
{
return (((pixelWidth * bitsPerPixel + 31) / 32) * 4);
}
// Map the individual image tiles to the large image
// in parallel. Any kind of raw image manipulation can be
// done here because we are not attempting to access any
// WPF controls from multiple threads.
byte[] TileImages(Task<byte[]>[] sourceImages)
{
byte[] largeImage = new byte[largeImagePixelHeight * largeImageStride];
int tileImageStride = tilePixelWidth * 4; // hard coded to 32bpp
Random rand = new Random();
Parallel.For(0, rowCount * colCount, (i) =>
{
// Pick one of the images at random for this tile.
int cur = rand.Next(0, sourceImages.Length);
byte[] pixels = sourceImages[cur].Result;
// Get the starting index for this tile.
int row = i / colCount;
int col = (int)(i % colCount);
int idx = ((row * (largeImageStride * tilePixelHeight)) + (col * tileImageStride));
// Write the pixels for the current tile. The pixels are not contiguous
// in the array, therefore we have to advance the index by the image stride
// (minus the stride of the tile) for each scanline of the tile.
int tileImageIndex = 0;
for (int j = 0; j < tilePixelHeight; j++)
{
// Write the next scanline for this tile.
for (int k = 0; k < tileImageStride; k++)
{
largeImage[idx++] = pixels[tileImageIndex++];
}
// Advance to the beginning of the next scanline.
idx += largeImageStride - tileImageStride;
}
});
return largeImage;
}
}
}
Imports System.Threading.Tasks
Imports System.Windows
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Partial Public Class MainWindow : Inherits Window
Dim fileCount As Integer
Dim colCount As Integer
Dim rowCount As Integer
Dim tilePixelHeight As Integer
Dim tilePixelWidth As Integer
Dim largeImagePixelHeight As Integer
Dim largeImagePixelWidth As Integer
Dim largeImageStride As Integer
Dim format As PixelFormat
Dim palette As BitmapPalette = Nothing
Public Sub New()
InitializeComponent()
' For this example, values are hard-coded to a mosaic of 8x8 tiles.
' Each tile Is 50 pixels high and 66 pixels wide and 32 bits per pixel.
colCount = 12
rowCount = 8
tilePixelHeight = 50
tilePixelWidth = 66
largeImagePixelHeight = tilePixelHeight * rowCount
largeImagePixelWidth = tilePixelWidth * colCount
largeImageStride = largeImagePixelWidth * (32 / 8)
Me.Width = largeImagePixelWidth + 40
image.Width = largeImagePixelWidth
image.Height = largeImagePixelHeight
End Sub
Private Sub button_Click(sender As Object, e As RoutedEventArgs) _
Handles button.Click
' For best results use 1024 x 768 jpg files at 32bpp.
Dim files() As String = System.IO.Directory.GetFiles("C:\Users\Public\Pictures\Sample Pictures\", "*.jpg")
fileCount = files.Length
Dim images(fileCount - 1) As Task(Of Byte())
For i As Integer = 0 To fileCount - 1
Dim x As Integer = i
images(x) = Task.Factory.StartNew(Function() LoadImage(files(x)))
Next
' When they have all been loaded, tile them into a single byte array.
'var tiledImage = Task.Factory.ContinueWhenAll(
' images, (i) >= TileImages(i));
' Dim tiledImage As Task(Of Byte()) = Task.Factory.ContinueWhenAll(images, Function(i As Task(Of Byte())) TileImages(i))
Dim tiledImage = Task.Factory.ContinueWhenAll(images, Function(i As Task(Of Byte())()) TileImages(i))
' We are currently on the UI thread. Save the sync context and pass it to
' the next task so that it can access the UI control "image1".
Dim UISyncContext = TaskScheduler.FromCurrentSynchronizationContext()
' On the UI thread, put the bytes into a bitmap and
' display it in the Image control.
Dim t3 = tiledImage.ContinueWith(Sub(antecedent)
' Get System DPI.
Dim m As Matrix = PresentationSource.FromVisual(Application.Current.MainWindow).CompositionTarget.TransformToDevice
Dim dpiX As Double = m.M11
Dim dpiY As Double = m.M22
' Use the default palette in creating the bitmap.
Dim bms As BitmapSource = BitmapSource.Create(largeImagePixelWidth,
largeImagePixelHeight,
dpiX,
dpiY,
format,
palette,
antecedent.Result,
largeImageStride)
image.Source = bms
End Sub, UISyncContext)
End Sub
Public Function LoadImage(filename As String) As Byte()
' Use the WPF BitmapImage class to load and
' resize the bitmap. NOTE: Only 32bpp formats are supported correctly.
' Support for additional color formats Is left as an exercise
' for the reader. For more information, see documentation for ColorConvertedBitmap.
Dim bitmapImage As New BitmapImage()
bitmapImage.BeginInit()
bitmapImage.UriSource = New Uri(filename)
bitmapImage.DecodePixelHeight = tilePixelHeight
bitmapImage.DecodePixelWidth = tilePixelWidth
bitmapImage.EndInit()
format = bitmapImage.Format
Dim size As Integer = CInt(bitmapImage.Height * bitmapImage.Width)
Dim stride As Integer = CInt(bitmapImage.Width * 4)
Dim dest(stride * tilePixelHeight - 1) As Byte
bitmapImage.CopyPixels(dest, stride, 0)
Return dest
End Function
Function Stride(pixelWidth As Integer, bitsPerPixel As Integer) As Integer
Return (((pixelWidth * bitsPerPixel + 31) / 32) * 4)
End Function
' Map the individual image tiles to the large image
' in parallel. Any kind of raw image manipulation can be
' done here because we are Not attempting to access any
' WPF controls from multiple threads.
Function TileImages(sourceImages As Task(Of Byte())()) As Byte()
Dim largeImage(largeImagePixelHeight * largeImageStride - 1) As Byte
Dim tileImageStride As Integer = tilePixelWidth * 4 ' hard coded To 32bpp
Dim rand As New Random()
Parallel.For(0, rowCount * colCount, Sub(i)
' Pick one of the images at random for this tile.
Dim cur As Integer = rand.Next(0, sourceImages.Length)
Dim pixels() As Byte = sourceImages(cur).Result
' Get the starting index for this tile.
Dim row As Integer = i \ colCount
Dim col As Integer = i Mod colCount
Dim idx As Integer = ((row * (largeImageStride * tilePixelHeight)) + (col * tileImageStride))
' Write the pixels for the current tile. The pixels are Not contiguous
' in the array, therefore we have to advance the index by the image stride
' (minus the stride of the tile) for each scanline of the tile.
Dim tileImageIndex As Integer = 0
For j As Integer = 0 To tilePixelHeight - 1
' Write the next scanline for this tile.
For k As Integer = 0 To tileImageStride - 1
largeImage(idx) = pixels(tileImageIndex)
idx += 1
tileImageIndex += 1
Next
' Advance to the beginning of the next scanline.
idx += largeImageStride - tileImageStride
Next
End Sub)
Return largeImage
End Function
End Class
Aby utworzyć przykład, utwórz projekt aplikacji WPF w Visual Studio i nadaj mu nazwę WPF_CS1 (dla projektu C# WPF) lub WPF_VB1 (dla projektu Visual Basic WPF). Następnie wykonaj poniższe czynności:
W widoku projektu przeciągnij kontrolkę Image z przybornika do lewego górnego rogu powierzchni projektowej. W polu tekstowym Nazwa okna Właściwości nadaj kontrolce nazwę "image".
Przeciągnij kontrolkę Button z przybornika do lewej dolnej części okna aplikacji. W widoku XAML określ Content właściwość przycisku jako "Utwórz mozaikę" i określ jej Width właściwość jako "100". Połączenie zdarzenie Click z procedurą obsługi zdarzeń
button_Click
zdefiniowaną w kodzie przykładu<Button>
przez dodanieClick="button_Click"
go do elementu . W polu tekstowym Nazwa okna Właściwości nadaj kontrolce nazwę "button".Zastąp całą zawartość pliku MainWindow.xaml.cs lub MainWindow.xaml.vb kodem z tego przykładu. W przypadku projektu WPF języka C# upewnij się, że nazwa obszaru roboczego jest zgodna z nazwą projektu.
Przykład odczytuje obrazy JPEG z katalogu o nazwie C:\Users\Public\Pictures\Sample Pictures\. Utwórz katalog i umieść w nim niektóre obrazy lub zmień ścieżkę, aby odwołać się do innego katalogu zawierającego obrazy.
Ten przykład ma pewne ograniczenia. Na przykład obsługiwane są tylko obrazy 32-bitowe na piksel; obrazy w innych formatach są uszkodzone przez BitmapImage obiekt podczas operacji zmiany rozmiaru. Ponadto obrazy źródłowe muszą być większe niż rozmiar kafelka. W kolejnym ćwiczeniu możesz dodać funkcję do obsługi wielu formatów pikseli i rozmiarów plików.
Konstruktory
TaskScheduler() |
Inicjuje element TaskScheduler. |
Właściwości
Current |
TaskScheduler Pobiera element skojarzony z aktualnie wykonywanym zadaniem. |
Default |
Pobiera domyślne TaskScheduler wystąpienie udostępniane przez platformę .NET. |
Id |
Pobiera unikatowy identyfikator dla tego TaskSchedulerelementu . |
MaximumConcurrencyLevel |
Wskazuje maksymalny poziom TaskScheduler współbieżności, który jest w stanie obsługiwać. |
Metody
Equals(Object) |
Określa, czy dany obiekt jest taki sam, jak bieżący obiekt. (Odziedziczone po Object) |
Finalize() |
Zwalnia wszystkie zasoby skojarzone z tym harmonogramem. |
FromCurrentSynchronizationContext() |
Tworzy obiekt TaskScheduler skojarzony z bieżącym SynchronizationContextelementem . |
GetHashCode() |
Służy jako domyślna funkcja skrótu. (Odziedziczone po Object) |
GetScheduledTasks() |
Tylko w przypadku obsługi debugera generuje wyliczenie Task wystąpień aktualnie w kolejce do harmonogramu oczekującego na wykonanie. |
GetType() |
Type Pobiera wartość bieżącego wystąpienia. (Odziedziczone po Object) |
MemberwiseClone() |
Tworzy płytkią kopię bieżącego Objectelementu . (Odziedziczone po Object) |
QueueTask(Task) |
Kolejkuje element Task do harmonogramu. |
ToString() |
Zwraca ciąg reprezentujący bieżący obiekt. (Odziedziczone po Object) |
TryDequeue(Task) |
Próbuje usunąć kolejkę Task elementu, który został wcześniej w kolejce do tego harmonogramu. |
TryExecuteTask(Task) |
Próby wykonania podanego Task w tym harmonogramie. |
TryExecuteTaskInline(Task, Boolean) |
Określa, czy podane Task dane można wykonać synchronicznie w tym wywołaniu i czy może, wykonuje je. |
Zdarzenia
UnobservedTaskException |
Występuje, gdy nieobserwowany wyjątek zadania błędu ma spowodować wyzwolenie zasad eskalacji wyjątków, które domyślnie zakończą proces. |
Dotyczy
Bezpieczeństwo wątkowe
Wszystkie elementy członkowskie typu abstrakcyjnego TaskScheduler są bezpieczne wątkowo i mogą być używane jednocześnie z wielu wątków.