System.Threading.Tasks.TaskScheduler osztály
Ez a cikk kiegészítő megjegyzéseket tartalmaz az API referenciadokumentációjához.
Az TaskScheduler osztály egy feladatütemezőt jelöl. A feladatütemezők biztosítják, hogy egy tevékenység munkája végül végrehajtásra kerül.
Az alapértelmezett feladatütemező munkalopást biztosít a terheléselosztáshoz, a szálinjektáláshoz/kivezetéshez a maximális átviteli sebességhez és az általánosan jó teljesítményhez. A legtöbb forgatókönyvhöz elegendőnek kell lennie.
Az TaskScheduler osztály az összes testreszabható ütemezési logika bővítménypontjaként is szolgál. Ez olyan mechanizmusokat is magában foglal, mint a tevékenységek ütemezése a végrehajtáshoz, és hogy az ütemezett tevékenységek hogyan legyenek kitéve a hibakeresőknek. Ha speciális funkciókra van szüksége, létrehozhat egy egyéni ütemezőt, és engedélyezheti azt adott feladatokhoz vagy lekérdezésekhez.
Az alapértelmezett feladatütemező és a szálkészlet
A párhuzamos feladattár és a PLINQ alapértelmezett ütemezője az osztály által képviselt .NET-szálkészletet használja a ThreadPool feladatok várólistára helyezéséhez és végrehajtásához. A szálkészlet a típus által Task biztosított információk alapján hatékonyan támogatja a párhuzamos feladatok és lekérdezések által gyakran képviselt részletes párhuzamosságot (rövid élettartamú munkaegységeket).
A globális üzenetsor és a helyi üzenetsorok
A szálkészlet minden alkalmazástartományban egy globális FIFO -munkasort (first-in, first-out) tart fenn a szálakhoz. Amikor egy program meghívja a ThreadPool.QueueUserWorkItem (vagy ThreadPool.UnsafeQueueUserWorkItem) metódust, a rendszer ezt a megosztott üzenetsort helyezi el, és végül a következő, elérhetővé váló szálra helyezi. A .NET-keretrendszer 4-től kezdve ez az üzenetsor egy, az ConcurrentQueue<T> osztályhoz hasonló zárolásmentes algoritmust használ. Ezzel a zárolásmentes implementációval a szálkészlet kevesebb időt tölt az üzenetsorok és az üzenetsorok munkaelemeinek megszüntetésekor. Ez a teljesítménybeli előny minden olyan program számára elérhető, amely a szálkészletet használja.
A legfelső szintű tevékenységek, amelyek nem egy másik tevékenység kontextusában lettek létrehozva, ugyanúgy kerülnek a globális várólistára, mint bármely más munkaelemre. A beágyazott vagy gyermek feladatokat azonban, amelyek egy másik tevékenység kontextusában jönnek létre, meglehetősen eltérően kezelik. A gyermek- vagy beágyazott feladat egy helyi üzenetsorra kerül, amely arra a szálra vonatkozik, amelyen a szülőfeladatot végrehajtja. A szülőtevékenység lehet legfelső szintű tevékenység, vagy egy másik tevékenység gyermeke is lehet. Ha ez a szál készen áll a további munkára, először a helyi üzenetsorban jelenik meg. Ha a munkaelemek ott várnak, gyorsan elérhetők. A helyi üzenetsorok a gyorsítótár helyének megőrzése és a versengés csökkentése érdekében az utolsó előtti, első előtti sorrendben (LIFO) érhetők el. További információ a gyermekfeladatokról és a beágyazott feladatokról: Csatolt és leválasztott gyermekfeladatok.
A helyi üzenetsorok használata nem csak csökkenti a globális üzenetsorra nehezedő nyomást, hanem kihasználja az adat helyének előnyeit is. A helyi üzenetsor munkaelemei gyakran hivatkoznak olyan adatstruktúrákra, amelyek fizikailag közel vannak egymáshoz a memóriában. Ezekben az esetekben az adatok már az első feladat futtatása után a gyorsítótárban vannak, és gyorsan elérhetők. A párhuzamos LINQ (PLINQ) és az Parallel osztály egyaránt széles körben használ beágyazott és gyermekfeladatokat, és a helyi munkasorok használatával jelentős gyorsításokat érhet el.
Munkalopás
A .NET-keretrendszer 4-től kezdve a szálkészlet egy munkalopó algoritmust is tartalmaz, amely segít biztosítani, hogy egyetlen szál sem tétlenül üljön, míg mások továbbra is dolgoznak az üzenetsoraikban. Ha egy szálkészlet-szál készen áll a további munkára, először a helyi üzenetsor fejére, majd a globális üzenetsorra, majd a többi szál helyi üzenetsorára néz. Ha egy munkaelemet egy másik szál helyi üzenetsorában talál, először heurisztikus elemeket alkalmaz annak érdekében, hogy hatékonyan tudja futtatni a munkát. Ha lehet, akkor a munkaelemet a farkából (FIFO sorrendben) törli. Ez csökkenti az egyes helyi üzenetsorokon való versengést, és megőrzi az adat honosságát. Ez az architektúra segít a szálkészlet terheléselosztásának hatékonyabb működésében, mint a korábbi verziókban.
Hosszan futó feladatok
Előfordulhat, hogy kifejezetten meg szeretné akadályozni, hogy egy feladat helyi üzenetsorba kerüljön. Előfordulhat például, hogy egy adott munkaelem viszonylag hosszú ideig fog futni, és valószínűleg blokkolja a helyi üzenetsor összes többi munkaelemét. Ebben az esetben megadhatja a System.Threading.Tasks.TaskCreationOptions beállítást, amely arra utal az ütemező számára, hogy a tevékenységhez további szálra lehet szükség, hogy az ne blokkolja a többi szál vagy munkaelem előrehaladtát a helyi üzenetsoron. Ezzel a beállítással teljesen elkerülheti a szálkészletet, beleértve a globális és a helyi üzenetsorokat is.
Tevékenységbesorolás
Bizonyos esetekben, amikor egy Task várakozik, előfordulhat, hogy szinkron módon hajtja végre a várakozási műveletet végrehajtó szálon. Ez növeli a teljesítményt azáltal, hogy megakadályozza egy további szál szükségességét, és ehelyett használja a meglévő szálat, amely egyébként blokkolva lett volna. Az újraküldés miatti hibák elkerülése érdekében a feladatbesorolás csak akkor történik meg, ha a várakozási cél az adott szál helyi üzenetsorában található.
Szinkronizálási környezet megadása
Ezzel a TaskScheduler.FromCurrentSynchronizationContext módszerrel megadhatja, hogy egy tevékenységet ütemezni kell egy adott szálon való futtatásra. Ez olyan keretrendszerekben hasznos, mint a Windows Forms és a Windows megjelenítési alaprendszer, ahol a felhasználói felület objektumaihoz való hozzáférés gyakran a felhasználói felület objektumát létrehozó szálon futó kódra korlátozódik.
Az alábbi példa egy TaskScheduler.FromCurrentSynchronizationContext Windows megjelenítési alaprendszer (WPF) alkalmazás metódusával ütemez egy feladatot ugyanazon a szálon, amelyen a felhasználói felület vezérlője létre lett hozva. A példa egy mozaikképet hoz létre, amely véletlenszerűen van kiválasztva egy adott könyvtárból. A WPF-objektumok a képek betöltésére és átméretezésére szolgálnak. A nyers képpontok ezután egy olyan feladatnak lesznek átadva, amely hurkot For használ a képpontadatok nagy egy bájtos tömbbe való írásához. Nincs szükség szinkronizálásra, mert két csempe sem foglalja el ugyanazokat a tömbelemeket. A csempék bármilyen sorrendben megírhatók, mert a pozíciójuk a többi csempétől függetlenül számítható ki. A nagyméretű tömb ezután egy felhasználói felületen futó feladatnak lesz átadva, ahol a képpontadatok betöltődnek egy képvezérlőbe.
A példa áthelyezi az adatokat a felhasználói felületi szálról, párhuzamos hurkok és Task objektumok használatával módosítja őket, majd átadja azokat egy, a felhasználói felületen futó feladatnak. Ez a megközelítés akkor hasznos, ha a feladat párhuzamos kódtárával olyan műveleteket kell végrehajtania, amelyeket a WPF API nem támogat, vagy nem elég gyorsak. Képmozaik létrehozásának másik módja a WPF-ben egy vezérlő használata System.Windows.Controls.WrapPanel és képek hozzáadása. A WrapPanel csempék elhelyezésének munkáját a kezelő kezeli. Ez a munka azonban csak a felhasználói felületen végezhető el.
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
A példa létrehozásához hozzon létre egy WPF-alkalmazásprojektet a Visual Studióban, és nevezze el WPF_CS1 (C# WPF-projekt esetén) vagy WPF_VB1 (Visual Basic WPF-projekt esetén). Ezután tegye a következőket:
Tervező nézetben húzzon egy vezérlőt Image az Eszközkészletből a tervezőfelület bal felső sarkába. A Tulajdonságok ablak Név szövegmezőjében adja meg a vezérlő "képének" nevét.
Húzza a vezérlőt Button az eszközkészletből az alkalmazás ablakának bal alsó részére. XAML nézetben adja meg a Content gomb tulajdonságát "Mozaik készítése" értékként, és adja meg a tulajdonságát Width "100" értékként. Csatlakozás az eseményt Click a
button_Click
példa kódjában definiált eseménykezelővel az<Button>
elem hozzáadásávalClick="button_Click"
. A Tulajdonságok ablak Név szövegmezőjében adja meg a vezérlő "gombját".Cserélje le a MainWindow.xaml.cs vagy MainWindow.xaml.vb fájl teljes tartalmát a példában szereplő kódra. C# WPF-projekt esetén győződjön meg arról, hogy a munkaterület neve megegyezik a projekt nevével.
A példa JPEG-képeket olvas be egy C:\Users\Public\Pictures\Sample Pictures nevű könyvtárból. Hozza létre a könyvtárat, és helyezzen bele néhány képet, vagy módosítsa az elérési utat, hogy más, képeket tartalmazó könyvtárra hivatkozzon.
Ez a példa bizonyos korlátozásokkal rendelkezik. Például csak a képpontonként 32 bites képek támogatottak; más formátumú képeket az BitmapImage objektum az átméretezési művelet során megsérül. Emellett a forráslemezképek méretének nagyobbnak kell lennie, mint a csempe mérete. További gyakorlatként több képpontformátumot és fájlméretet is kezelhet funkciókkal.
Visszajelzés
https://aka.ms/ContentUserFeedback.
Hamarosan elérhető: 2024-ben fokozatosan kivezetjük a GitHub-problémákat a tartalom visszajelzési mechanizmusaként, és lecseréljük egy új visszajelzési rendszerre. További információ:Visszajelzés küldése és megtekintése a következőhöz: