TaskScheduler Класс
Определение
Важно!
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Представляет объект, обрабатывающий низкоуровневую постановку задач в очередь на потоки.
public ref class TaskScheduler abstract
public abstract class TaskScheduler
type TaskScheduler = class
Public MustInherit Class TaskScheduler
- Наследование
-
TaskScheduler
Примеры
В следующем примере создается настраиваемый планировщик задач, ограничивающий количество потоков, используемых приложением. Затем он запускает два набора задач и отображает сведения о задаче и потоке, в котором выполняется задача.
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
Комментарии
Экземпляр TaskScheduler класса представляет планировщик задач. Планировщик задач гарантирует, что работа над задачей в итоге будет выполнена.
Планировщик заданий по умолчанию основан на пуле потоков .NET Framework 4, который обеспечивает перехват работы для балансировки нагрузки, вставку/удаление потока для максимальной пропускной способности и общее повышение производительности. Его должно быть достаточно для большинства сценариев.
Класс TaskScheduler также служит точкой расширения для всей настраиваемой логики планирования. Сюда входят такие механизмы, как планирование задачи для выполнения и предоставление отладчикам запланированных задач. Если требуются специальные функциональные возможности, можно создать настраиваемый планировщик и включить его для конкретных задач или запросов.
Содержание этой статьи
Планировщик задач по умолчанию и пул потоков
Глобальная очередь и локальные очереди
Кража работы
Длительные задачи
Встраивание задач
Указание контекста синхронизации
Планировщик задач по умолчанию и пул потоков
Планировщик по умолчанию для библиотеки параллельных задач и PLINQ использует пул потоков .NET, представленный классом, для постановки ThreadPool в очередь и выполнения работы. Пул потоков использует сведения, предоставляемые типом Task , для эффективной поддержки детализированного параллелизма (кратковременных единиц работы), которые часто представляют параллельные задачи и запросы.
Глобальная очередь и локальные очереди
Пул потоков поддерживает глобальную рабочую очередь FIFO (first-in- first-out) для потоков в каждом домене приложения. Всякий раз, когда программа вызывает ThreadPool.QueueUserWorkItem метод (или ThreadPool.UnsafeQueueUserWorkItem), работа помещается в эту общую очередь и в конечном итоге отключается в следующий поток, который становится доступным. Начиная с платформа .NET Framework 4, эта очередь использует алгоритм без блокировки, похожий на ConcurrentQueue<T> класс. Используя эту реализацию без блокировки, пул потоков тратит меньше времени, когда он помещает рабочие элементы в очередь и отменяет очереди. Это преимущество производительности доступно для всех программ, использующих пул потоков.
Задачи верхнего уровня, которые являются задачами, не созданными в контексте других задач, помещаются в глобальную очередь так же, как и другие рабочие элементы. Однако вложенные или дочерние задачи, создаваемые в контексте других задач, обрабатываются по-другому. Дочерняя или вложенная задача помещается в локальную очередь, относящуюся к потоку, в котором выполняется родительская задача. Родительская задача может быть задачей верхнего уровня или дочерней задачей другой задачи. Когда этот поток готов для дополнительной работы, сначала он выполняет поиск в локальной очереди. Если в ней существует ожидающие рабочие элементы, к ним возможен быстрый доступ. Доступ к локальным очередям осуществляется в порядке последнего и первого выхода (LIFO), чтобы сохранить локальность кэша и уменьшить состязание. Дополнительные сведения о дочерних задачах и вложенных задачах см. в разделе "Присоединенные и отсоединяемые дочерние задачи".
Использование локальных очередей не только снижает нагрузку на глобальную очередь, но и использует преимущества локальности данных. Рабочие элементы в локальной очереди часто ссылаются на структуры данных, физически расположенные рядом друг с другом в памяти. В таких случаях данные уже размещаются в кэше после выполнения первой задачи и могут быть доступны быстро. Параллельные LINQ (PLINQ) и Parallel класс используют вложенные задачи и дочерние задачи широко и достигают значительных скоростей с помощью локальных рабочих очередей.
Кража работы
Начиная с платформа .NET Framework 4, пул потоков также имеет алгоритм кражи рабочих данных, чтобы убедиться, что потоки не находятся в состоянии простоя, а другие по-прежнему работают в своих очередях. Когда поток из пула потоков готов для дополнительной работы, сначала он выполняет поиск в своей локальной очереди, далее в глобальной очереди, а затем в локальных очередях других потоков. При обнаружении рабочего элемента в локальной очереди другого потока он сначала применяет эвристику, чтобы убедиться, что он может эффективно выполнить эту работу. Если он может, то выводит этот рабочий элемент с конца очереди (в порядке ФИФО). Это уменьшает конкуренцию внутри каждой из локальных очередей и сохраняет локальность данных. Эта архитектура помогает эффективнее работать с балансировкой нагрузки пула потоков, чем предыдущие версии.
Длительные задачи
Может потребоваться явно запретить помещение задачи в локальную очередь. Например, вы знаете, что определенный рабочий элемент будет выполняться довольно долго и может заблокировать другие рабочие элементы в локальной очереди. В таком случае можно указать параметр System.Threading.Tasks.TaskCreationOptions, который подсказывает планировщику, что для задачи может потребоваться дополнительный поток, чтобы она не блокировала дальнейший ход работы других потоков или рабочих элементов в локальной очереди. Используя этот параметр, вы полностью избегаете пула потоков, включая глобальные и локальные очереди.
Встраивание задач
В некоторых случаях при Task ожидании он может выполняться синхронно в потоке, который выполняет операцию ожидания. Это повышает производительность, предотвращая необходимость в дополнительном потоке и вместо этого используя существующий поток, который был бы заблокирован в противном случае. Чтобы предотвратить ошибки из-за повторного входа, встраивание задач происходит только в том случае, если целевой объект ожидания найден в локальной очереди соответствующего потока.
Указание контекста синхронизации
С помощью метода TaskScheduler.FromCurrentSynchronizationContext можно указать, что задачу необходимо планировать для запуска в определенном потоке. Это полезно на платформах, например Windows Forms и Windows Presentation Foundation, где доступ к объектам пользовательского интерфейса часто ограничен кодом, выполняемым в том же потоке, в котором был создан этот объект пользовательского интерфейса.
В следующем примере метод в приложении Windows Presentation Foundation (WPF) используется TaskScheduler.FromCurrentSynchronizationContext для планирования задачи в том же потоке, в который был создан элемент управления пользовательского интерфейса. В примере создается мозаика изображений, которые случайным образом выбираются из указанного каталога. Объекты WPF используются для загрузки и изменения размера изображений. Затем необработанные пиксели передаются задаче, которая использует For цикл для записи данных пикселей в большой однобайтовый массив. Синхронизация не требуется, так как две плитки не занимают одни и те же элементы массива. Плитки также могут быть записаны в любом порядке, так как их положение вычисляется независимо от любой другой плитки. Затем большой массив передается задаче, которая выполняется в потоке пользовательского интерфейса, где данные пикселей загружаются в элемент управления Image.
В примере данные перемещаются из потока пользовательского интерфейса, изменяются с помощью параллельных циклов и Task объектов, а затем передаются обратно в задачу, которая выполняется в потоке пользовательского интерфейса. Этот подход полезен, если необходимо использовать библиотеку параллельных задач для выполнения операций, которые либо не поддерживаются API WPF, либо недостаточно быстры. Еще одним способом создания мозаики изображений в WPF является использование System.Windows.Controls.WrapPanel элемента управления и добавление к нему изображений. Обрабатывает WrapPanel работу размещения плиток. Однако эту работу можно выполнять только в потоке пользовательского интерфейса.
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
Чтобы создать пример, создайте проект приложения WPF в Visual Studio и назовите его WPF_CS1 (для проекта C# WPF) или WPF_VB1 (для проекта Visual Basic WPF). После этого выполните описанные ниже действия.
В режиме конструктора перетащите Image элемент управления с панели элементов в левый верхний угол области конструктора. В текстовом поле "Имя " окна "Свойства " присвойте элементу управления "image".
Перетащите Button элемент управления с панели элементов в нижнюю левую часть окна приложения. В представлении XAML укажите Content свойство кнопки как "Сделать мозаику" и укажите его Width свойство как "100". Click Подключение событие с обработчиком
button_Click
событий, определенным в коде примера, путем добавленияClick="button_Click"
в<Button>
элемент. В текстовом поле "Имя " окна "Свойства " назовите элемент управления "button".Замените все содержимое файла MainWindow.xaml.cs или MainWindow.xaml.vb кодом из этого примера. Для проекта C# WPF убедитесь, что имя рабочей области соответствует имени проекта.
Пример считывает изображения JPEG из каталога C:\Users\Public\Pictures\Sample Pictures\. Создайте каталог и поместите в него некоторые изображения или измените путь для ссылки на другой каталог, содержащий изображения.
В этом примере есть некоторые ограничения. Например, поддерживаются только 32-разрядные изображения на пиксель; Изображения в других форматах повреждены BitmapImage объектом во время операции изменения размера. Кроме того, исходные изображения должны быть больше размера плитки. В качестве дополнительного упражнения можно добавить функции для обработки нескольких форматов пикселей и размеров файлов.
Конструкторы
TaskScheduler() |
Инициализирует объект TaskScheduler. |
Свойства
Current |
Получает объект TaskScheduler, связанный с выполняемой в настоящий момент задачей. |
Default |
Получает экземпляр TaskScheduler по умолчанию, предоставляемый .NET. |
Id |
Получает уникальный идентификатор данного объекта TaskScheduler. |
MaximumConcurrencyLevel |
Указывает максимальный уровень параллелизма, который может поддерживаться данным планировщиком TaskScheduler. |
Методы
Equals(Object) |
Определяет, равен ли указанный объект текущему объекту. (Унаследовано от Object) |
Finalize() |
Освобождает все ресурсы, связанные с данным планировщиком. |
FromCurrentSynchronizationContext() |
Создает TaskScheduler для связывания с текущим элементом SynchronizationContext. |
GetHashCode() |
Служит хэш-функцией по умолчанию. (Унаследовано от Object) |
GetScheduledTasks() |
Создает перечисляемый объект экземпляров Task, которые в настоящее время находятся в очереди планировщика, ожидая выполнения (только для поддержки отладки). |
GetType() |
Возвращает объект Type для текущего экземпляра. (Унаследовано от Object) |
MemberwiseClone() |
Создает неполную копию текущего объекта Object. (Унаследовано от Object) |
QueueTask(Task) |
Ставит объект Task в очередь планировщика. |
ToString() |
Возвращает строку, представляющую текущий объект. (Унаследовано от Object) |
TryDequeue(Task) |
Пытается удалить из очереди задачу Task, ранее поставленную в очередь данного планировщика. |
TryExecuteTask(Task) |
Пытается выполнить предоставленную задачу Task в этом планировщике. |
TryExecuteTaskInline(Task, Boolean) |
Определяет, можно ли выполнить предоставленную задачу Task в этом вызове синхронно, и если возможно, выполняет ее. |
События
UnobservedTaskException |
Создается при активации политики эскалации исключений из-за непредвиденного исключения задачи, завершившейся сбоем. По умолчанию из-за этой политики процесс будет прерван. |
Применяется к
Потокобезопасность
Все члены абстрактного TaskScheduler типа являются потокобезопасными и могут использоваться из нескольких потоков одновременно.