Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.
Klasa TaskScheduler reprezentuje harmonogram zadań. Harmonogram zadań gwarantuje, że praca zadania zostanie ostatecznie wykonana.
Domyślny harmonogram zadań zapewnia kradzież pracy na potrzeby równoważenia obciążenia, iniekcji wątku/wycofywania w celu uzyskania maksymalnej przepływności oraz 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 wszystkich dostosowywalnych logiki planowania. Obejmuje to mechanizmy, takie jak planowanie zadania pod kątem wykonywania oraz sposób uwidaczniania zaplanowanych zadań dla debugerów. Jeśli potrzebujesz specjalnej funkcjonalności, możesz utworzyć niestandardowy harmonogram i włączyć go dla określonych zadań lub zapytań.
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ć szczegółowe równoległości (krótkotrwałe jednostki pracy), które często reprezentują zadania równoległe i zapytania.
Kolejka globalna a kolejki lokalne
Pula wątków utrzymuje globalną kolejkę zadań typu FIFO (pierwsze weszło, pierwsze wyszło) 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 współdzielonej kolejce i ostatecznie przenoszona do następnego wątku, który stanie się dostępny. Począwszy od programu .NET Framework 4, ta kolejka używa algorytmu bez blokady przypominającego klasę ConcurrentQueue<T> . Dzięki tej implementacji bez blokady pula wątków poświęca 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 zagnieżdżone lub podrzędne zadania, 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, na 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 dalszej pracy, najpierw patrzy w kolejkę lokalną. Jeśli elementy robocze oczekują tam, można uzyskać do nich szybki dostęp. 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 oraz zadaniach zagnieżdżonych, zobacz Zadania podrzędne połączone i odłączone.
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 Parallel LINQ (PLINQ), jak i Parallel klasa intensywnie używają zagnieżdżonych zadań i zadań podrzędnych, a także osiągają znaczne przyspieszenie przy użyciu lokalnych kolejek roboczych.
Kradzież pracy
Począwszy od .NET Framework 4, pula wątków obejmuje również algorytm przechwytywania pracy, aby upewnić się, że żadne wątki nie pozostają bezczynne, podczas gdy inne nadal mają pracę w swoich kolejkach. Gdy wątek puli wątków jest gotowy do dalszej pracy, najpierw sprawdza początek swojej kolejki lokalnej, następnie kolejkę globalną, a na końcu lokalne kolejki innych wątków. Jeśli znajdzie zadanie w lokalnej kolejce innego wątku, najpierw stosuje heurystyki, aby upewnić się, że może wykonać zadanie wydajnie. Jeśli to możliwe, usuwa element roboczy z kolejki z końca (w kolejności FIFO). Zmniejsza to rywalizację o każdą kolejkę lokalną i zachowuje lokalność danych. Ta architektura umożliwia lepsze równoważenie obciążenia w puli wątków niż było to 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ć opcję System.Threading.Tasks.TaskCreationOptions, która zawiera wskazówkę harmonogramowi, że dla zadania może być wymagany dodatkowy wątek, aby zadanie nie blokowało postępu innych wątków ani elementów roboczych w kolejce lokalnej. Korzystając z tej opcji, całkowicie unikasz puli wątków, w tym kolejek globalnych i lokalnych.
Podkreślenie zadania
W niektórych przypadkach, gdy czeka się na element Task, może być on wykonywany synchronicznie na wątku wykonującym operację oczekiwania. Zwiększa to wydajność, zapobiegając konieczności użycia dodatkowego wątku i zamiast tego wykorzystując istniejący wątek, który inaczej zostałby zablokowany. Aby zapobiec błędom spowodowanym ponowną wejściowością, włączanie zadań występuje tylko wtedy, gdy docelowe zadanie oczekiwania zostanie znalezione w lokalnej kolejce odpowiedniego wątku.
Określanie kontekstu synchronizacji
Można użyć metody TaskScheduler.FromCurrentSynchronizationContext, aby określić, że zadanie powinno być zaplanowane do uruchomienia na określonym wątku. Jest to przydatne w strukturach, takich jak Windows Forms i Windows Presentation Foundation, gdzie dostęp do obiektów interfejsu użytkownika jest często ograniczony do kodu działającego w tym samym wątku, w którym utworzono obiekt interfejsu użytkownika.
W poniższym przykładzie użyto metody TaskScheduler.FromCurrentSynchronizationContext w aplikacji Windows Presentation Foundation (WPF), aby zaplanować zadanie w tym samym wątku, w którym utworzono kontrolkę interfejsu użytkownika. W przykładzie tworzona jest mozaika obrazów, które są losowo wybierane 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 z dwóch kafelków nie zajmuje tych samych elementów w tablicy. Kafelki można również zapisywać w dowolnej kolejności, ponieważ ich położenie jest obliczane 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 obrazu.
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 musisz 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 utworzenia mozaiki obrazu w WPF jest użycie System.Windows.Controls.WrapPanel kontrolki i dodanie do niej obrazów. WrapPanel zajmuje się pracą związaną z pozycjonowaniem kafelków. Tę pracę można jednak 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;
}
}
}
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 programie 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 Toolbox do lewego górnego rogu obszaru projektowego. 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łącz zdarzenie Click z obsługą zdarzeń
button_Clickzdefiniowaną w kodzie przykładu, dodającClick="button_Click"do elementu<Button>. 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 kilka obrazów 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 każdy obraz źródłowy musi być większy niż rozmiar kafelka. W kolejnym ćwiczeniu możesz dodać funkcje do obsługi wielu formatów pikseli i rozmiarów plików.