Рус Eng Cn Перевести страницу на:  
Please select your language to translate the article


You can just close the window to don't translate
Библиотека
ваш профиль

Вернуться к содержанию

Программные системы и вычислительные методы
Правильная ссылка на статью:

Потокобезопасные вызовы элементов управления в обогащенных клиентских приложениях

Гибадуллин Руслан Фаршатович

ORCID: 0000-0001-9359-911X

кандидат технических наук

доцент кафедры компьютерных систем Казанского национального исследовательского технического университета им. А.Н. Туполева-КАИ (КНИТУ-КАИ)

420015, Россия, республика Татарстан, г. Казань, ул. Большая Красная, 55, каб. 432

Gibadullin Ruslan Farshatovich

PhD in Technical Science

Associate Professor of the Computer Systems Department of Kazan National Research Technical University named after A.N. Tupolev-KAI (KNRTU-KAI)

420015, Russia, Republic of Tatarstan, Kazan, Bolshaya Krasnaya str., 55, office 432

rfgibadullin@kai.ru
Другие публикации этого автора
 

 

DOI:

10.7256/2454-0714.2022.4.39029

EDN:

IAXOMA

Дата направления статьи в редакцию:

25-10-2022


Дата публикации:

30-12-2022


Аннотация: Когда была выпущена первая версия .NET Framework в обогащенных клиентских приложениях существовал шаблон, ориентированный на циклы обработки сообщений, где использовалась встроенная очередь для передачи единиц исполнения из рабочих потоков. Далее было разработано обобщенное решение ISynchronizeInvoke, в рамках которого поток-источник может поставить делегат в очередь к потоку-приемнику и, как необязательный вариант, ожидать завершения этого делегата. После введения поддержки асинхронных страниц в архитектуру ASP.NET шаблон ISynchronizeInvoke не походил, так как асинхронные ASP.NET-страницы не сопоставлены с единственным потоком. Это стало причиной создания ещё более обобщенного решения – SynchronizationContext, что и является предметом исследования. В статье на практических примерах представлено, как следует обновлять элементы пользовательского интерфейса из рабочих потоков, не нарушая потокобезопасность пользовательского приложения. В этом аспекте предлагаются решения: с применением методов Beginlnvoke или Invoke для постановки этого делегата в очередь сообщений потока пользовательского интерфейса; с захватом контекста синхронизации потока пользовательского интерфейса посредством свойства Current класса SynchronizationContext; с применением устаревшего класса BackgroundWorker, обеспечивающий неявный захват контекста синхронизации потока пользовательского интерфейса. Не оставлена без внимания особенность реализации абстрактного класса SynchronizationContext в платформе ASP.NET. Сформированы практические рекомендации по использованию механизма маршализации на примере разработки мультиклиентного чата с централизованным сервером.


Ключевые слова:

программирование, многопоточность, Windows Forms, Windows Presentation Foundation, Universal Windows Platform, контекст синхронизации, делегаты, NET Framework, параллельное программирование, шаблоны проектирования

Abstract: When the first version of the .NET Framework was released, there was a pattern in enriched client applications that focused on message processing loops, where an embedded queue was used to pass execution units from worker threads. A generalized ISynchronizeInvoke solution was then developed in which the source thread could queue a delegate to the destination thread and, as an optional option, wait for that delegate to complete. After asynchronous page support was introduced into the ASP.NET architecture, the ISynchronizeInvoke pattern did not work because asynchronous ASP.NET pages are not mapped to a single thread. This was the reason for creating an even more generalized solution – SynchronizationContext, which is the subject of the research. The article uses practical examples to show how to update UI elements from worker threads without breaking thread-safety of the user application. Solutions proposed in this aspect are: using Beginlnvoke or Invoke methods to put this delegate into the UI thread message queue; capturing the UI thread synchronization context via the Current property of the SynchronizationContext class; using the deprecated BackgroundWorker class, which provides an implicit capture of the UI thread synchronization context. The peculiarity of implementation of the SynchronizationContext abstract class in ASP.NET platform is not left unnoticed. Practical recommendations on the use of marshalling mechanism on the example of development of multiclient chat with a centralized server are formulated.


Keywords:

programming, multithreading, Windows Forms, Windows Presentation Foundation, Universal Windows Platform, synchronization context, delegates, NET Framework, parallel programming, design patterns

Введение

Многопоточность может повысить производительность в приложениях Windows Presentation Foundation (WPF), Universal Windows Platform (UWP) и Windows Forms, но доступ к элементам управления не является потокобезопасным. Многопоточность может представлять код для серьезных и сложных ошибок. Два или более потока, управляющих элементом управления, могут привести к нестабильному состоянию и вызвать условия состязаний. Данная статья посвящена раскрытию темы вызова элементов управления потокобезопасным образом на примере разработки мультиклиентного чата с централизованным сервером.

Многопоточность в обогащенных клиентских приложениях

В приложениях WPF, UWP и Windows Forms выполнение длительных по времени операций в главном потоке снижает отзывчивость приложения, потому что главный поток обрабатывает также цикл сообщений, который отвечает за визуализацию и под­держку событий клавиатуры и мыши. Поэтому в обогащенных клиентских приложениях, где реализуется различный функционал, то и дело приходится сталкиваться с многопоточностью. Популярный подход предусматривает настройку “рабочих” потоков для выполнения длительных по времени операций. Код в рабочем потоке запускает длительную операцию и по ее завершении обновляет пользовательский интерфейс. Тем не менее все обогащенные клиентские приложения поддерживают потоковую модель, в кото­рой элементы управления пользовательского интерфейса могут быть доступны только из создавшего их потока (обычно главного потока пользовательского интерфейса). Нарушение данного правила приводит либо к непредсказуемому поведению, либо к генерации исключения. Последнее можно отключить заданием свойства Control.CheckForIllegalCrossThreadCalls значением false.

Следовательно, когда нужно обновить пользовательский интерфейс из рабочего потока, запрос должен быть перенаправлен потоку пользовательского интерфейса (формально это называется маршализацией). Вот как это выглядит:

  • в приложении WPF вызовите метод Beginlnvoke или Invoke на объекте Dispatcher элемента;
  • в приложении UWP вызовите метод RunAsync или Invoke на объекте Dispatcher;
  • в приложении Windows Forms вызовите метод Beginlnvoke или Invoke на элементе управления.

Все упомянутые методы принимают делегат, ссылающийся на метод, который требу­ется запустить. Методы Beginlnvoke/RunAsync работают путем постановки этого де­легата в очередь сообщений потока пользовательского интерфейса (та же очередь, которая обрабатывает события, поступающие от клавиатуры, мыши и таймера). Метод Invoke делает то же самое, но затем блокируется до тех пор, пока сообщение не будет прочита­но и обработано потоком пользовательского интерфейса. По указанной причине метод Invoke позволяет получить возвращаемое значение из метода. Если возвращаемое зна­чение не требуется, то методы Beginlnvoke/RunAsync предпочтительнее из-за того, что они не блокируют вызывающий компонент и не привносят возможность возникно­вения взаимоблокировки [1,2].

Вы можете представлять себе, что при вызове метода Application.Run выполняется следующий псевдокод:

while (приложение не завершено)

{

Ожидать появления чего-нибудь в очереди сообщений.

Что-то получено: к какому виду сообщений оно относится?

Сообщение клавиатуры/мыши -> запустить обработчик событий.

Пользовательское сообщение Beginlnvoke -> выполнить делегат.

Пользовательское сообщение Invoke ->

выполнить делегат и отправить результат.

}

Цикл такого вида позволяет рабочему потоку маршализовать делегат для выполнения в потоке пользовательского интерфейса.

В целях демонстрации предположим, что имеется окно WPF с текстовым полем по имени txtMessage, содержимое которого должно быть обновлено рабочим пото­ком после выполнения длительной задачи (эмулируемой с помощью вызова метода Thread.Sleep). Ниже приведен необходимый код:

void Main()

{

new MyWindow().ShowDialog();

}

partial class MyWindow : Window

{

TextBox txtMessage;

public MyWindow()

{

InitializeComponent();

new Thread (Work).Start();

}

void Work()

{

Thread.Sleep (5000); // Simulate time-consuming task

UpdateMessage ("The answer");

}

void UpdateMessage (string message)

{

Action action = () => txtMessage.Text = message;

Dispatcher.BeginInvoke (action);

}

void InitializeComponent()

{

SizeToContent = SizeToContent.WidthAndHeight;

WindowStartupLocation = WindowStartupLocation.CenterScreen;

Content = txtMessage = new TextBox { Width=250, Margin=new Thickness (10), Text="Ready" };

}

}

После запуска показанного кода немедленно появляется окно. Спустя пять секунд текстовое поле обновляется. Для случая Windows Forms код будет похож, но только в нем вызывается метод Beginlnvoke объекта Form:

void UpdateMessage (string message)

{

Action action = () => txtMessage.Text = message;

this.BeginInvoke (action);

}

Допускается иметь множество потоков пользовательского интерфейса, если каж­дый из них владеет своим окном. Основным сценарием может служить приложение с несколькими высокоуровневыми окнами, которое часто называют приложением с однодокументным интерфейсом (Single Document Interface – SDI), например, Microsoft Word. Каждое окно SDI обычно отображает себя как отдельное “приложение” в па­нели задач и по большей части оно функционально изолировано от других окон SDI. За счет предоставления каждому такому окну собственного потока пользовательско­го интерфейса окна становятся более отзывчивыми.

Контексты синхронизации

В пространстве имен System.ComponentModel определен абстрактный класс SynchronizationContext, который делает возможным обобщение маршализации потоков. Необходимость в таком обобщении подробно описана в статье Стивена Клири [3].

В API-интерфейсах для мобильных и настольных приложений (UWP, WPF и Windows Forms) определены и созданы экземпляры подклассов SynchronizationContext, которые можно получить через статическое свойство SynchronizationContext.Current (при выполнении в потоке пользовательского интерфейса). Захват этого свойства позволяет позже “отправлять” сообщения элемен­там управления пользовательского интерфейса из рабочего потока:

partial class MyWindow : Window

{

TextBox txtMessage;

SynchronizationContext _uiSyncContext;

public MyWindow()

{

InitializeComponent();

// Capture the synchronization context for the current UI thread:

_uiSyncContext = SynchronizationContext.Current;

new Thread (Work).Start();

}

void Work()

{

Thread.Sleep (5000); // Simulate time-consuming task

UpdateMessage ("The answer");

}

void UpdateMessage (string message)

{

// Marshal the delegate to the UI thread:

_uiSyncContext.Post (_ => txtMessage.Text = message, null);

}

void InitializeComponent()

{

SizeToContent = SizeToContent.WidthAndHeight;

WindowStartupLocation = WindowStartupLocation.CenterScreen;

Content = txtMessage = new TextBox { Width=250, Margin=new Thickness (10), Text="Ready" };

}

}

Удобство в том, что один и тот же подход работает со всеми обогащенными API-интерфейсами. Правда не все реализации SynchronizationContext гарантируют порядок выполнения делегатов или их синхронизацию (см. таблицу). Реализации SynchronizationContext на основе UI этим условиям удовлетворяют, тогда как ASP.NET SynchronizationContext обеспечивает только синхронизацию.

Таблица. Сводное описание реализаций SynchronizationContext

Выполнение делегатов в определенном потоке

Делегаты выполняются по одному за раз

Делегаты выполняются в порядке очереди

Send может напрямую вызывать делегат

Post может напрямую вызывать делегат

Windows Forms

Да

Да

Да

Если вызывается из UI-потока

Никогда

WPF/Silverlight

Да

Да

Да

Если вызывается из UI-потока

Никогда

По умолчанию

Нет

Нет

Нет

Всегда

Никогда

ASP.NET

Нет

Да

Нет

Всегда

Всегда

SynchronizationContext по умолчанию не гарантирует ни порядка выполнения, ни синхронизации, где базовая реализация методов Send и Post выглядит следующим образом:

public virtual void Send (SendOrPostCallback d, object state)

{

d (state);

}

public virtual void Post (SendOrPostCallback d, object state)

{

ThreadPool.QueueUserWorkItem (d.Invoke, state);

}

Как видим, Send просто выполняет делегат в вызывающем потоке, Post делает то же самое, но используя пул потоков для асинхронности. Но в API-интерфейсах данные методы переопределены и реализуют концепцию очереди сообщений: вызов метода Post эквивалентен вызову Beginlnvoke на объекте Dispatcher (для WPF) или Control (для Windows Forms), а метод Send является эквивалентом Invoke.

Класс BackgroundWorker

Класс BackgroundWorker позволяет обогащенным клиент­ским приложениям запускать рабочий поток и сообщать о проценте выполненной ра­боты без необходимости в явном захвате контекста синхронизации [1]. Например:

var worker = new BackgroundWorker { WorkerSupportsCancellation = true };

worker.DoWork += (sender, args) =>

{ // Выполняется в рабочем потоке

if (args.Cancel) return;

Thread.Sleep(1000);

args.Result = 123;

};

worker.RunWorkerCompleted += (sender, args) =>

{ // Выполняется в потоке пользовательского интерфейса

// Здесь можно безопасно обновлять элементы управления

// пользовательского интерфейса

if (args.Cancelled)

Console.WriteLine("Cancelled");

else if (args.Error != null)

Console.WriteLine("Error: " + args.Error.Message);

else

Console.WriteLine("Result is: " + args.Result);

};

worker.RunWorkerAsync(); // Захватывает контекст синхронизации

// и запускает операцию

Метод RunWorkerAsync запускает операцию, инициируя событие DoWork в рабочем потоке из пула. Он также захватывает контекст синхронизации, и когда опе­рация завершается (или отказывает), через данный контекст генерируется событие RunWorkerCompleted (подобно признаку продолжения).

Класс BackgroundWorker порождает крупномодульный параллелизм, при котором событие DoWork инициируется полностью в рабочем потоке. Если в этом обработчи­ке событий нужно обновлять элементы управления пользовательского интерфейса (помимо отправки сообщения о проценте выполненных работ), тогда придется использовать Beginlnvoke или похожий метод.

Апробация шаблона ISynchronizeInvoke на примере разработки прикладного приложения

Применим описанный ранее механизм маршализации на примере разработки мультиклиентного чата с централизованным сервером (за основу взято программное решение, предложенное веб-разработчиком Эндрю Поцю (Andrew Pociu), автором уже закрывшегося проекта geekpedia).

Отбросив некоторые детали, сфокусируем внимание на возможностях приложения и на участках кода, где актуальны описанные ранее принципы.

Приложение обеспечивает обмен сообщениями по компьютерной сети в режиме реального времени. В основе приложения – два модуля: клиент и сервер (на рисунках 1 и 2 представлены пользовательские интерфейсы данных модулей). Клиенты могут указать IP-адрес сервера и порт, через который будут осуществлять обмен сообщениями после подключения к серверу, а также задать своё имя для размещения его в титульной части отправленных сообщений. Все подключенные к серверу клиенты могут одновременно отправлять сообщения на сервер и видеть сообщения друг друга, посредством широковещательной рассылки, которую имитирует сервер. Серверный модуль содержит информацию обо всех подключенных клиентах, ждёт сообщения от каждого и отправляет входящие сообщения для всех, имитируя широковещательную рассылку.

Рисунок 1. Пользовательский интерфейс клиентского модуля

Рисунок 2. Пользовательский интерфейс серверного модуля

Так как в клиентском модуле используется сетевое взаимодействие, потоковые и многопоточные объекты, то имеем следующее объявление:

using System.Net;

using System.Net.Sockets;

using System.IO;

using System.Threading;

Большинство наших полей и методов делаются приватными, так как обращение к ним из сторонних объектов, а также из дочерних классов не требуется. В классе Form1 используются следующие типы:

  • StreamReader и StreamWriter, чтобы отправлять и получать сообщения;
  • TcpClient, чтобы подключиться к серверу;
  • Thread, чтобы параллельно обеспечивался прием сообщений от сервера.

StreamWriter swSender;

StreamReader srReceiver;

TcpClient tcpServer;

Thread thrMessaging

Метод BtnConnectClick отвечает за нажатие кнопки «Connect».

void BtnConnectClick(object sender, EventArgs e)

{

if (connected == false)

{

InitializeConnection();

}

else

{

CloseConnection("Disconnected at user's request.");

}

}

Инициализация соединения обеспечивается вызовом метода InitializeConnection.

void InitializeConnection()

{

try

{

tcpServer = new TcpClient();

tcpServer.Connect(IPAddress.Parse(txtIp.Text),

ushort.Parse(txtPort.Text));

connected = true;

txtIp.Enabled = false;

txtPort.Enabled = false;

txtUser.Enabled = false;

txtMessage.Enabled = true;

btnSend.Enabled = true;

btnConnect.Text = "Disconnect";

swSender = new StreamWriter(tcpServer.GetStream())

{ AutoFlush = true };

swSender.WriteLine(txtUser.Text);

thrMessaging = new Thread(ReceiveMessages);

thrMessaging.Start();

}

catch (Exception exc)

{

UpdateLog("Error: " + exc.Message);

}

}

Заданием свойства AutoFlush значением true обеспечивается запись буфера StreamWriter во внутренний поток всякий раз после вызова метода WriteLine на адаптере потока. При возникновении исключения вызывается метод UpdateLog для вывода сообщения об ошибке в окне лога клиента. Обратите внимание, что вызов метода выполняется в потоке пользовательского интерфейса, а следовательно отсутствует небходимость в маршализации делагата, ссылающего на данный метод.

Метод ReceiveMessages, отвечающий за прием сообщений от сервера, выполняется в отдельном потоке thrMessaging.

void ReceiveMessages()

{

try

{

srReceiver = new StreamReader(tcpServer.GetStream());

string ConResponse = srReceiver.ReadLine();

if (ConResponse[0] == '1')

{

Invoke(new Action(UpdateLog), "Connected successfully!");

}

else

{

string Reason = "Not connected: ";

Reason += ConResponse.Substring(2, ConResponse.Length - 2);

Invoke(new Action(CloseConnection), Reason);

return;

}

while (connected)

{

string s = srReceiver.ReadLine();

if (s == "Administrator: Server is stopped.")

Invoke(new Action(CloseConnection), s);

else

Invoke(new Action(UpdateLog), s);

}

}

catch

{

Invoke(new Action(CloseConnection),

"The connection to the server is complete.");

}

}

Данный метод реализует следующий протокол. Первый символ сообщения, полученный от сервера, сигнализирует об успешном или неуспешном соединении клиента. Ответ от сервера может быть двух типов:

  • "1", при успешном соединении;
  • "0|{Error message}", при неуспешном соединении.

Если соединение неуспешно, то вызывается метод CloseConnection (завершение соединения), в противном случае – UpdateLog (обновление лога). Оба метода обновляют элемент управления пользовательского интерфейса. А так как в приложениях Windows Forms поддерживается потоковая модель, в которой элементы управления пользовательского интерфейса доступны только из создавшего их потока, то посредством метода Invoke обеспечивается выполнение методов в контексте потока пользовательского интерфейса.

В случае возникновения исключения при выполнении метода ReceiveMessages происходит вызов метода CloseConnection с указанием сообщения об ошибке в окне лога клиента. Чтение строк из потока NetworkStream обеспечивается посредством адаптера потока StreamReader.

Метод UpdateLog выполняет обновление лога в окне клиента.

void UpdateLog(string message)

{

txtLog.AppendText(message + "rn");

}

Отправка сообщения серверу обеспечивается нажатием на кнопку «Send» или на клавишу «Enter».

void BtnSendClick(object sender, EventArgs e)

{

SendMessage();

}

void MessageKeyPress(object sender, KeyPressEventArgs e)

{

if (e.KeyChar == (char)13)

SendMessage();

}

Метод SendMessage обеспечивает отправку сообщения на сервер при наличии по крайне мере одного символа в текстовом поле txtMessage.

void SendMessage()

{

try

{

if (txtMessage.Text.Length > 0)

{

swSender.WriteLine(txtMessage.Text);

txtMessage.Text = "";

}

}

catch (Exception exc)

{

CloseConnection($"Error: {exc.Message}");

}

}

Метод CloseConnection меняет статус рабочих элементов формы и закрывает адаптеры потоков. При этом закрытие адаптеров приводит к автоматическому закрытию потоков, лежащих в их основе.

void CloseConnection(string reason)

{

txtIp.Enabled = true;

txtPort.Enabled = true;

txtUser.Enabled = true;

txtMessage.Enabled = false;

btnSend.Enabled = false;

btnConnect.Text = "Connect";

connected = false;

swSender.Close();

srReceiver.Close();

txtLog.AppendText(reason + "rn");

}

Далее представлены конструктор формы Form1 и метод OnApplicationExit, который вызывается при возникновении события ApplicationExit (закрытие приложения).

public Form1()

{

Application.ApplicationExit += new EventHandler(OnApplicationExit);

InitializeComponent();

}

void OnApplicationExit(object sender, EventArgs e)

{

if (connected == true)

{

connected = false;

swSender.Close();

srReceiver.Close();

}

}

Так как в серверном модуле используются сетевое взаимодействие, потоковые и многопоточные объекты, конкурентная коллекция и объекты, представляющие кодировку символов Юникода, то имеем следующее объявление:

using System;

using System.Net;

using System.Net.Sockets;

using System.IO;

using System.Threading;

using System.Collections.Concurrent;

using System.Collections.Generic;

using System.Text;

В методе BtnListenClick задается новый обработчик для события StatusChanged. Данный обработчик опосредованно через метод UpdateStatus выполняет обновление лога серверного модуля посредством изменения содержимое текстового поля txtLog.

readonly ChatServer mainServer;

public Form1()

{

mainServer = new ChatServer();

Application.ApplicationExit +=

new EventHandler(OnApplicationExit);

InitializeComponent();

}

void BtnListenClick(object sender, EventArgs e)

{

if (txtIP.Enabled)

{

mainServer.StatusChanged +=

new StatusChangedEventHandler(MainServerStatusChanged);

mainServer.SetIPEndPoint(txtIP.Text, txtPort.Text);

mainServer.StartListening();

txtIP.Enabled = false;

txtPort.Enabled = false;

btnListen.Text = "Stop Listening";

txtLog.AppendText("Monitoring is started.rn");

}

else

mainServer.StopListener();

}

void MainServerStatusChanged(object sender, StatusChangedEventArgs e)

{

Invoke(new Action(UpdateStatus), e.eventMessage);

}

void UpdateStatus(string message)

{

txtLog.AppendText(message + "rn");

if (message == "Administrator: Listening is stopped.")

{

txtIP.Enabled = true;

txtPort.Enabled = true;

btnListen.Text = "Start Listening";

txtLog.AppendText("Monitoring is stopped.rn");

mainServer.StatusChanged -=

new StatusChangedEventHandler(MainServerStatusChanged);

}

}

Обратите внимание на наличие вызова Invoke в методе MainServerStatusChanged. Понимание в необходимости подобного вызова кроется в описании класса ChatServer, полное содержание которого выходит за рамки данной статьи. Отметим лишь главное, что вызов события StatusChanged выполняется из рабочих потоков, поэтому для обеспечения потокобезопасности при работе с текстовым полем txtLog требуется постановка делегата Action(UpdateStatus) в очередь сообщений потока пользовательского интерфейса.

Посмотреть полное описание проекта, а также апробировать разработанное приложение можно, скачав архив [4] из веб-ресурса автора текущей статьи. Следует обратить внимание, что исходный проект, предложенный изначально Эндрю Поцю, был оптимизирован и существенно доработан (удалены или заменены некоторые объекты, с целью устранения избыточности программного кода и обеспечения надежной работы клиентского и серверного модулей), например:

  • Хеш-таблицы htUsers и htConnections заменены конкурентной коллекцией ConcurrentDictionary<string, TcpClient> htUsers, которая обеспечивает потокобезопасность при одновременном обращении к коллекции из рабочих потоков (к сведению, данная коллекция содержит элементы представляющие имена пользователей (клиентов) и ассоциированных с ними объектов, предоставляющие клиентские подключения для сетевых служб протокола TCP).
  • Интерфейсы клиентского и серверного наделены возможностью указания порта для установки TCP-соединения.
  • Во избежание аварийного завершения клиентского или серверного модуля участки кода снабжены конструкциями try-catch, чтобы обрабатывать различного рода исключения и в окне лога выводить причину внезапного прекращения работы приложения.
  • Код метода OnStatusChanged заменен на

StatusChanged?.Invoke(null, e);

В многопоточных сценариях перед проверкой и вызовом делегат необходимо присваивать временной переменной во избежание ошибки, связанной с безопасностью потоков:

var temp = StatusChanged;

if (temp != null) temp(this, e);

Начиная с версии C# 6, ту же самую функциональность можно получить без переменной temp с помощью null - условной операции:

StatusChanged?.Invoke(this, е);

Будучи безопасным к потокам и лаконичным, теперь это наилучший общепринятый способ вызова событий.

Заключение

Немалое число концепций требуется изучить тем, кто впервые сталкивается с многопоточным программированием. Класс SynchronizationContext, который имеется в Microsoft .NET Framework, реализует концепцию контекста синхронизации независимо от платформы (будь то ASP.NET, Windows Forms, WPF, Silverlight или нечто другое) и является подспорьем для разработчиков желающих создавать потокобезопасные приложения. Понимание данной концепции полезно любому программисту. В статье дано краткое и содержательное понимание классу SynchronizationContext. Сформированы практические рекомендации по использованию механизма маршализации на примере разработки мультиклиентного чата с централизованным сервером. Взятый за основу исходный проект существенно доработан и является образцовым примером разработки приложения, где требуется обеспечить потоковую безопасность не только при обновлении элементов пользовательского интерфейса, но и в отношении использования коллекции объектов. Последнее достигается применением конкурентной коллекции, который реализован с применением легковесных средств синхронизации с исключением блокировок там, где они не нужны.

Предметом дальнейшего исследования на базе имеющего авторского задела [5-8] является наделение разработанного мультиклиентного чата протоколом защищенного взаимодействия клиентов с централизованным сервером с сохранением потоковой безопасности и надежности работы модулей.

Библиография
1.
2.
3.
4.
5.
6.
7.
8.
References
1.
2.
3.
4.
5.
6.
7.
8.

Результаты процедуры рецензирования статьи

В связи с политикой двойного слепого рецензирования личность рецензента не раскрывается.
Со списком рецензентов издательства можно ознакомиться здесь.

В представленной на рецензирование статье рассматриваются вопросы потокобезопасности при вызове элементов управления в приложениях Windows Presentation Foundation, Universal Windows Platform и Windows Forms, раскрывается механизм, обеспечивающий потокобезопасность.
Методология исследования базируется на изучении литературных источников по теме работы, рассмотрении фрагментов программных кодов.
Актуальность работы авторы статьи связывают с тем, что многопоточность может представлять код для серьезных и сложных ошибок, а несколько потоков, управляющих элементом управления, могут привести к нестабильному состоянию и вызвать условия состязаний.
Научная новизна рецензируемого исследования, по мнению рецензента заключается в обобщении, кратком и содержательном изложении сути потокобезопасных вызовов элементов управления в различных приложениях.
В статье структурно выделены следующие разделы: Введение, Многопоточность в обогащенных клиентских приложениях, Контексты синхронизации, BackgroundWorker, Заключение, Библиография.
Автор рассматривает процесс маршализации, приводит соответствующие программные коды, в публикации рассматриваются методы Beginlnvoke/RunAsync и Invoke, Send и Post, показываются их сходства и различия, отражается сводное описание реализаций SynchronizationContext в виде отдельной таблицы, в которой рассмотрены такие параметры как выполнение делегатов в определенном потоке, выполнение их по одному за раз, в порядке очереди, возможность в методах Send и Post напрямую вызывать делегат.
Библиографический список включает 2 источника – публикации зарубежных авторов по теме статьи.
В качестве замечания можно отметить, во-первых, использование авторами сокращений в названии статьи и заголовках отдельных её разделов на иностранном языке – это не лучшее решение, поскольку значительная часть читателей, не знакомая с используемыми англоязычными аббревиатурами не получает адекватной информации о содержании статьи и ее компонент. Во-вторых, в тексте статьи элементы приращения научного знания не раскрыты с достаточной детализацией, да и сама формулировка ключевой цели, отраженная в Заключении: «дать краткое и содержательное понимание классу SynchronizationContext» представляется более подходящей не для научной статьи, а популяризаторской публикации. В-третьих, вряд ли список литературы всего лишь из двух источников можно считать достаточным для отражения различных подходов к решению рассматриваемой статье, также вызывает сомнение уместность использования ссылки на первый источник применительно к наименованию структурных разделов публикации причем это сделано дважды, а на второй источник ссылка вообще не приведена – поэтому наличие апелляции к оппонентам в представленном материале отсутствует.
Рецензируемый материал соответствует направлению журнала «Программные системы и вычислительные методы», подготовлен на актуальную тему, содержит обобщения по рассматриваемой теме, может вызвать интерес у читателей, однако, по мнению рецензента статья нуждается в доработке в соответствие с высказанными замечаниями.

Замечания главного редактора от 06.11.2022: " Автор доработал рукопись в соответствии стребованиями реценезнетов".