Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Задача будет решаться с использованием графического пользовательского интерфейса, так что вы увидите, как применять "анонимные делегаты", позволяющие вторичным потокам обновлять первичный поток пользовательского интерфейса.
На заметку! При построении многопоточного приложения с графическим пользовательским интерфейсом вторичные потоки никогда не смогут напрямую обращаться к элементам управления пользовательского интерфейса. Причина в том, что элементы управления (кнопки, текстовые поля, метки, индикаторы хода работ и т.п.) привязаны к потоку, в котором они создавались. В следующем примере иллюстрируется один из способов обеспечения для вторичных потоков возможности получать доступ к элементам пользовательского интерфейса в безопасной к потокам манере. Во время рассмотрения ключевых слов async и await языка C# будет предложен более простой подход.
В целях иллюстрации создайте приложение Windows Presentation Foundation (WPF) по имени DataParallelismWithForEach, выбрав шаблон WPF Арр (.NET Core). Чтобы создать проект и добавить его к решению с помощью командной строки, используйте следующие команды:
dotnet new wpf -lang c# -n DataParallelismWithForEach
-o .DataParallelismWithForEach -f
net5.0
dotnet sln .Chapter15_AllProjects.sln add .DataParallelismWithForEach
На заметку! Инфраструктура Windows Presentation Foundation (WPF) в текущей версии .NET Core предназначена только для Windows и будет подробно рассматриваться в главах 24-28. Если вы еще не работали с WPF, то здесь описано все, что необходимо для данного примера. Разработка приложений WPF ведется в среде Visual Studio Code, хотя никаких визуальных конструкторов там не предусмотрено. Чтобы получить больший опыт разработки приложений WPF, рекомендуется использовать Visual Studio 2019.
Дважды щелкните на имени файла MainWindow.xaml в окне Solution Explorer и поместите в него показанное далее содержимое XAML:
<Window x:Class="DataParallelismWithForEach.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DataParallelismWithForEach"
mc:Ignorable="d"
Title="Fun with TPL" Height="400" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0">
Feel free to type here while the images are processed...
</Label>
<TextBox Grid.Row="1" Grid.Column="0" Margin="10,10,10,10"/>
<Grid Grid.Row="2" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Name="cmdCancel" Grid.Row="0" Grid.Column="0"
Margin="10,10,0,10"
Click="cmdCancel_Click">
Cancel
</Button>
<Button Name="cmdProcess" Grid.Row="0" Grid.Column="2"
Margin="0,10,10,10"
Click="cmdProcess_Click">
Click to Flip Your Images!
</Button>
</Grid>
</Grid>
</Window>
И снова пока не следует задаваться вопросом о том, что означает приведенная разметка или как она работает; вскоре вам придется посвятить немало времени на исследование WPF. Графический пользовательский интерфейс приложения состоит из многострочной текстовой области TextBox и одной кнопки Button (по имени cmdProcess). Текстовая область предназначена для ввода данных во время выполнения работы в фоновом режиме, иллюстрируя тем самым неблокирующую природу параллельной задачи.
В этом примере требуется дополнительный пакет NuGet (System.Drawing.Common). Чтобы добавить его в проект, введите следующую команду (целиком в одной строке) в окне командной строки (в каталоге, где находится файл решения) или в консоли диспетчера пакетов в Visual Studio:
dotnet add DataParallelismWithForEach package System.Drawing.Common
Дважды щелкнув на имени файла MainWindow.xaml.cs (может потребоваться развернуть узел MainWindow.xaml), добавьте в его начало представленные ниже операторы using:
// Обеспечить доступ к перечисленным ниже пространствам имен!
// (System.Threading.Tasks уже должно присутствовать благодаря
// выбранному шаблону.)
using System;
using System.Drawing;
using System.Threading.Tasks;
using System.Threading;
using System.Windows;
using System.IO;
На заметку! Вы должны обновить строку, передаваемую методу Directory.GetFiles(), чтобы в ней был указан конкретный путь к каталогу на вашей машине, который содержит файлы изображений. Для вашего удобства в каталог TestPictures включено несколько примеров изображений (поставляемых в составе операционной системы Windows).
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void cmdCancel_Click(object sender, EventArgs e)
{
// Код метода будет вскоре обновлен.
}
private void cmdProcess_Click(object sender, EventArgs e)
{
ProcessFiles();
this.Title = "Processing Complete";
}
private void ProcessFiles()
{
// Загрузить все файлы *.jpg и создать новый каталог
// для модифицированных данных.
// Получить путь к каталогу с исполняемым файлом.
// В режиме отладки VS 2019 текущим каталогом будет
// <каталог npoeктa>bindebugnet5.0 - windows.
// В случае VS Code или команды dotnet run текущим
// каталогом будет <каталог проекта>.
var basePath = Directory.GetCurrentDirectory();
var pictureDirectory =
Path.Combine(basePath, "TestPictures");
var outputDirectory =
Path.Combine(basePath, "ModifiedPictures");
// Удались любые существующие файлы.
if (Directory.Exists(outputDirectory))
{
Directory.Delete(outputDirectory, true);
}
Directory.CreateDirectory(outputDirectory);
string[] files = Directory.GetFiles(pictureDirectory,
"*.jpg", SearchOption.AllDirectories);
// Обработать данные изображений в блокирующей манере.
foreach (string currentFile in files)
{
string filename =
System.IO.Path.GetFileName(currentFile);
// Вывести идентификатор потока, обрабатывающего текущее изображение.
this.Title = $"Processing {filename}
on thread {Thread.CurrentThread.ManagedThreadId}";
using (Bitmap bitmap = new Bitmap(currentFile))
{
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
bitmap.Save(System.IO.Path.Combine(
outputDirectory, filename));
}
}
}
}
На заметку! В случае получения сообщения об ошибке, связанной с неоднозначностью имени Path между System.IO.Path и System.Windows.Shapes.Path, либо удалите оператор using для System.Windows.Shapes, либо добавьте System.IO к Path: System.IO.Path.Combine(...).
Обратите внимание, что метод ProcessFiles() выполнит поворот изображения в каждом файле *.jpg из