Маленький пример использования многопоточности в приложениях, содержащих графический пользовательский интерфейс (GUI). Продемонстрировано два способа обращения к элементам пользовательского интерфейса из рабочего потока в UI-поток. Графический интерфейс при этом не "подвисает".
.Net Framework 4.5
/* Sandbox.cs
* © Andrey Bushman, 2017
*
* Небольшой пример создания дополнительного потока,
* работающего параллельно с потоком пользовательского
* интерфейса (UI) и обновляющего этот интерфейс по мере
* необходимости. Дополнительных потоков можно создавать
* сколько угодно. В данном примере для простоты создаётся
* только один.
*
* В данном примере вместо прямого использоватия потока
* (Thread) я использую задачу (Task), которая в свою очередь
* использует пул потоков.
*
* Рабочий поток (т.е. задача) вычисляет текущую дату и время,
* после чего записывает их в ListBox, находящийся в потоке UI.
*
* Снятие/установка галочки "Do work" управляет
* стартом/завершением рабочего потока. Кнопка "Clear" очищает
* ListBox и заголовок окна.
*
* В консоль выводятся идентификаторы текущих потоков и маркер
* их принадлежности (или не принадлежности) к пулу потоков.
*
* В данном примере для обращения к потоку UI из рабочего
* потока я использую два способа: диспетчер и контекст
* синхронизации.
*
* В примере используется WPF, но всё то же самое применимо к
* WinForms и ASP.NET.
*/
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace Bushman.Sandbox {
class MyWindow : Window {
SynchronizationContext context = null;
public MyWindow() : base() {
Console.Title = "Sandbox";
string prefix = "Main Window";
Title = prefix;
Topmost = true;
Width = 300;
Height = 600;
ResizeMode = ResizeMode.NoResize;
WindowStartupLocation = WindowStartupLocation
.CenterScreen;
Grid grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions.Add(new RowDefinition());
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.ColumnDefinitions[1].Width = new GridLength(0,
GridUnitType.Auto);
grid.RowDefinitions[1].Height = new GridLength(0,
GridUnitType.Auto);
Content = grid;
Thickness margin = new Thickness(5, 5, 5, 5);
ListBox listbox = new ListBox();
listbox.Margin = margin;
grid.Children.Add(listbox);
listbox.SetValue(Grid.RowProperty, 0);
listbox.SetValue(Grid.ColumnProperty, 0);
listbox.SetValue(Grid.ColumnSpanProperty, 2);
CheckBox chBox = new CheckBox();
chBox.Margin = margin;
chBox.Content = "Do work";
grid.Children.Add(chBox);
chBox.SetValue(Grid.ColumnProperty, 0);
chBox.SetValue(Grid.RowProperty, 1);
Task task = null;
long i = 0;
chBox.Checked += (s, e) => {
task = new Task(() => {
using (task) {
i = 0;
while (Dispatcher.Invoke(
() => chBox.IsChecked == true) &&
i < long.MaxValue) {
// В рабочем потоке выполняем
// некоторую работу. Например -
// формируем строку текущих даты и
// времени.
string value = DateTime.Now
.ToString("yyyy-MM-dd hh:mm:ss"
);
// В данном примере мы мы можем
// имитировать длительную работу,
// либо заблокировать эту строку
// кода, если такая имитация нам не
// нужна:
//Thread.Sleep(TimeSpan.FromSeconds
// (1));
Console.WriteLine(
"Current thread Id: {0}. " +
"Is pull thread: {1}",
Thread.CurrentThread
.ManagedThreadId.ToString(),
Thread.CurrentThread
.IsThreadPoolThread);
// Результат наших "вычислений"
//записываем в поток UI
context.Post(_ => {
listbox.Items.Add(value);
Title = string.Format(
"{0}. Items Count: {1}",
prefix, i++.ToString());
Console.WriteLine(
"Current thread Id: {0}. " +
"Is pull thread: {1}",
Thread.CurrentThread
.ManagedThreadId.ToString(),
Thread.CurrentThread
.IsThreadPoolThread);
}, null);
}
}
});
task.Start();
};
chBox.IsChecked = false;
Button button = new Button();
button.Content = "Clear";
button.Margin = margin;
button.Padding = margin;
grid.Children.Add(button);
button.SetValue(Grid.ColumnProperty, 1);
button.SetValue(Grid.RowProperty, 1);
button.Click += (s, e) => {
listbox.Items.Clear();
Title = prefix;
i = 0;
};
EventHandler action = null;
action = (s, e) => {
context = SynchronizationContext.Current;
Activated -= action;
};
Activated += action;
NameScope.SetNameScope(this, new NameScope());
RegisterName(nameof(listbox), listbox);
RegisterName(nameof(chBox), chBox);
RegisterName(nameof(button), button);
}
}
class Sandbox {
[STAThread]
static void Main(string[] args) {
MyWindow win = new MyWindow();
Application app = new Application();
app.Run(win);
}
}
}
Результат работы кода выглядит следующим образом:
.Net Framework 4.5
/* Sandbox.cs
* © Andrey Bushman, 2017
*
* Небольшой пример создания дополнительного потока,
* работающего параллельно с потоком пользовательского
* интерфейса (UI) и обновляющего этот интерфейс по мере
* необходимости. Дополнительных потоков можно создавать
* сколько угодно. В данном примере для простоты создаётся
* только один.
*
* В данном примере вместо прямого использоватия потока
* (Thread) я использую задачу (Task), которая в свою очередь
* использует пул потоков.
*
* Рабочий поток (т.е. задача) вычисляет текущую дату и время,
* после чего записывает их в ListBox, находящийся в потоке UI.
*
* Снятие/установка галочки "Do work" управляет
* стартом/завершением рабочего потока. Кнопка "Clear" очищает
* ListBox и заголовок окна.
*
* В консоль выводятся идентификаторы текущих потоков и маркер
* их принадлежности (или не принадлежности) к пулу потоков.
*
* В данном примере для обращения к потоку UI из рабочего
* потока я использую два способа: диспетчер и контекст
* синхронизации.
*
* В примере используется WPF, но всё то же самое применимо к
* WinForms и ASP.NET.
*/
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace Bushman.Sandbox {
class MyWindow : Window {
SynchronizationContext context = null;
public MyWindow() : base() {
Console.Title = "Sandbox";
string prefix = "Main Window";
Title = prefix;
Topmost = true;
Width = 300;
Height = 600;
ResizeMode = ResizeMode.NoResize;
WindowStartupLocation = WindowStartupLocation
.CenterScreen;
Grid grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions.Add(new RowDefinition());
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.ColumnDefinitions.Add(new ColumnDefinition());
grid.ColumnDefinitions[1].Width = new GridLength(0,
GridUnitType.Auto);
grid.RowDefinitions[1].Height = new GridLength(0,
GridUnitType.Auto);
Content = grid;
Thickness margin = new Thickness(5, 5, 5, 5);
ListBox listbox = new ListBox();
listbox.Margin = margin;
grid.Children.Add(listbox);
listbox.SetValue(Grid.RowProperty, 0);
listbox.SetValue(Grid.ColumnProperty, 0);
listbox.SetValue(Grid.ColumnSpanProperty, 2);
CheckBox chBox = new CheckBox();
chBox.Margin = margin;
chBox.Content = "Do work";
grid.Children.Add(chBox);
chBox.SetValue(Grid.ColumnProperty, 0);
chBox.SetValue(Grid.RowProperty, 1);
Task task = null;
long i = 0;
chBox.Checked += (s, e) => {
task = new Task(() => {
using (task) {
i = 0;
while (Dispatcher.Invoke(
() => chBox.IsChecked == true) &&
i < long.MaxValue) {
// В рабочем потоке выполняем
// некоторую работу. Например -
// формируем строку текущих даты и
// времени.
string value = DateTime.Now
.ToString("yyyy-MM-dd hh:mm:ss"
);
// В данном примере мы мы можем
// имитировать длительную работу,
// либо заблокировать эту строку
// кода, если такая имитация нам не
// нужна:
//Thread.Sleep(TimeSpan.FromSeconds
// (1));
Console.WriteLine(
"Current thread Id: {0}. " +
"Is pull thread: {1}",
Thread.CurrentThread
.ManagedThreadId.ToString(),
Thread.CurrentThread
.IsThreadPoolThread);
// Результат наших "вычислений"
//записываем в поток UI
context.Post(_ => {
listbox.Items.Add(value);
Title = string.Format(
"{0}. Items Count: {1}",
prefix, i++.ToString());
Console.WriteLine(
"Current thread Id: {0}. " +
"Is pull thread: {1}",
Thread.CurrentThread
.ManagedThreadId.ToString(),
Thread.CurrentThread
.IsThreadPoolThread);
}, null);
}
}
});
task.Start();
};
chBox.IsChecked = false;
Button button = new Button();
button.Content = "Clear";
button.Margin = margin;
button.Padding = margin;
grid.Children.Add(button);
button.SetValue(Grid.ColumnProperty, 1);
button.SetValue(Grid.RowProperty, 1);
button.Click += (s, e) => {
listbox.Items.Clear();
Title = prefix;
i = 0;
};
EventHandler action = null;
action = (s, e) => {
context = SynchronizationContext.Current;
Activated -= action;
};
Activated += action;
NameScope.SetNameScope(this, new NameScope());
RegisterName(nameof(listbox), listbox);
RegisterName(nameof(chBox), chBox);
RegisterName(nameof(button), button);
}
}
class Sandbox {
[STAThread]
static void Main(string[] args) {
MyWindow win = new MyWindow();
Application app = new Application();
app.Run(win);
}
}
}
Результат работы кода выглядит следующим образом:
1 комментарий:
Замечательный пример. Мало где найдешь подобное - в основном примеры с использованием Dispatcher, который сам по себе затрачивает много времени на обновление GUI. "Прикрутил" данный код из статьи к своим нуждам и работа плагина стала в разы быстрее!
Теперь нужно 105 раз это прочитать, чтобы понять и запомнить =))
Отправить комментарий