#c_sharp #winforms #многопоточность
Я создал приложение для перепрошивки устройства по интерфейсу UART
Хочу добавить необязательное, но желаемое взаимодействие с программой:
Остановка работы потока по нажатию на кнопку "Остановить выполнение".
Программа работает следующим образом:
private void Execute_Commands_Button_Click(object sender, EventArgs e)
// Нажатие на кнопку "Выполнить команды"
{
// Очистить данные
Clear_Data();
// Если нет ошибок в полях
if (Check_All_Fields())
{
// Если есть соединение с портом
if (Check_Port())
{
// Если параметры работы с портом установлены успешно
if (Set_Port_Settings())
{
// Обновить данные Hex- файла
Refresh_Hex_File_Data();
// Если устройство не синхронизировано
if (!Is_Synchronised)
{
// Поток для синхронизации
Thread Sync_Thread;
// Поток для синхронизации
Sync_Thread = new Thread(delegate()
{
// Выключить элементы управления
Set_Enable(false);
// Попытаться синхронизировать
Try_To_Synchronise_Device(Convert.ToInt32(
This_Common.Read_From_Registry(This_Common.Key_Sync_Attempts)));
// Включить элементы управления
Set_Enable(true);
});
// Записываем поток как текущий
Active_Thread = Sync_Thread;
// Запуск потока
Sync_Thread.Start();
// Ожидание завершения работы потока
Sync_Thread.Join();
}
// Если устройство синхронизировано
if (Is_Synchronised)
{
// Обновить список команд
Refresh_Commands();
// Попытаться послать команды на порт
Try_To_Send_Commands();
}
// Если устройство не синхронизировано
else
{
// Вывести ошибку
This_Common.Show_Error_Message("Не удалось синхронизироваться
с устройством!");
}
// Закрыть порт
Selected_Port.Close();
}
}
}
}
В функции void Set_Enable(bool) Все элементы управления становятся неактивными и
включается только кнопка "Остановить выполнение".
при нажатии на эту кнопку активный поток закрывается: Active_Thread.Abort().
Проблема заключается в следующем: после ожидания выполнения группы методов в отдельном
потоке я теряю возможность обрабатывать нажатия на кнопку "Остановить выполнение".
Как это можно исправить?
Вариант с Task не сработал. Код следующий:
// Обработчик запроса отмены задачи
CancellationTokenSource Source = new CancellationTokenSource();
// Переменная отключения выполнения задачи
CancellationToken Cancel_Token;
// Задача "Синхронизировать"
Task Sync_Task;
// Задать задачу
Sync_Task = new Task(delegate()
{
// Попытаться синхронизироваться
Try_To_Synchronise_Device(Convert.ToInt32(This_Common.Read_From_Registry(This_Common.Key_Sync_Attempts)));
}, Cancel_Token);
// Запуск задачи
Sync_Task.Start();
// Ожидаем выполнение задачи
Source.Wait();
При клике на кнопку "Остановить выполнение": Source.Cancel();
Ответы
Ответ 1
Я тоже наклепал пример попроще. public partial class Form1 : Form { public Form1() { InitializeComponent(); } //источник токена отмены CancellationTokenSource _tokenSource; //Запуск private async void _buttonStart_Click(object sender, EventArgs e) { //через него будем оповещать о ходе выполнения задачи Progressprogess = new Progress (text => this._labelOutput.Text = text); //готовим токен отмены _tokenSource = new CancellationTokenSource(); CancellationToken cancelToken = _tokenSource.Token; //запускаем долгую задачу try { this._labelOutput.Text = "Начинаем..."; //обратите внимание на передачу токена отмены, и экземпл. прогресса this._labelOutput.Text = await Task.Run(() => DoSomething(cancelToken, progess), cancelToken); } catch (OperationCanceledException) { this._labelOutput.Text = "Задача отменена."; } catch (Exception ex) { this._labelOutput.Text = $"В задаче произошла ошибка: {ex.Message}"; } } //Отмена private void _buttonCancel_Click(object sender, EventArgs e) { _tokenSource.Cancel(); } //Эта самая долгая задача, обратите внимание на тип параметра progess он IProgress private string DoSomething(CancellationToken cancelToken, IProgress progess) { for (int i = 0; i < 6; i++) { //сообщаем о прогрессе progess.Report($"Этап: {i}"); //задержка между этапами 1 сек. cancelToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(1)); //здесь будет выброшено исключение в случае нажатия на кнопку отмены cancelToken.ThrowIfCancellationRequested(); } return "Готово!"; } } Ответ 2
В общем случае вы не можете корректно остановить произвольную программу снаружи принудительно (некорректно-то можно через TerminateThread сделать). Остановка должна быть кооперативной. Это означает, что ваш поток должен периодически проверять не пытаются ли его остановить и если пытаются - то остановиться. Применительно к вашему коду это означает что вам нужно переписать метод Try_To_Synchronise_Device так, чтобы он поддерживал отмену и остановку. В простейшей случае для остановки будет достаточно изменчивого булевого поля: private volatile bool stop = false; // ... while (!stop) { // что-то делаем } В более сложных случаях можно воспользоваться токенами отмены: private CancellationTokenSource stop = new CancellationTokenSource(); // ... while (!stop.IsCancellationRequested) { // что-то делаем } Но зачастую программа "висит" не в вашем коде, а в чужом, делая блокирующий внешний вызов. В таком случае для завершения потока нужно этот вызов как-то прервать. Это также нельзя сделать принудительно - только кооперативно, т.е. внешний код должен предусматривать прерывание долгих операций. В простейшем случае внешний код просто принимает токен отмены и дальше делает все сам: foo.Bar(baz, "Hello, world!", 42, stop.Token); // Вызов прервется при отмене токена В более сложных случаях у внешнего кода будет какой-то метод который нужно вызвать для отмены. В таком случае вам пригодится метод Register: var token = stop.Token; using (token.Register(() => foo.Cancel())) foo.Bar(baz, "Hello, world!", 42); Иногда внешний объект можно закрыть чтобы прервать все операции с ним: var token = stop.Token; using (token.Register(() => foo.Dispose())) foo.Bar(baz, "Hello, world!", 42);Ответ 3
Наклепал пример, как работать с асинхронной операцией и как её отменять public class MyWindow : Window { Button btStart; Button btStop; TextBlock tb; CancellationTokenSource currentOperationTioken; Task currentOperation; public MyWindow() { // Тут просто создает контроды на форме и подписываем на нужные события this.Width = 450; this.Height = 200; this.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen; var mainPanel = new StackPanel() { Orientation = Orientation.Horizontal, HorizontalAlignment= System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch, }; btStart = new Button() { Content = "Start", Width=150 }; btStop = new Button() {Content = "Stop", Width=150}; btStop.IsEnabled = false; tb = new TextBlock() {Width=150}; mainPanel.Children.Add(btStart); mainPanel.Children.Add(btStop); mainPanel.Children.Add(tb); this.AddChild(mainPanel); // подптсь на события btStart.Click+=StartClick; btStop.Click+=StopClick; } асинхронный обработчик, тут мы запускаем нашу операцию и передаем ей токен для отмены, ждем конца операции и обновляем состояние кнопок. private async void StartClick(object sender, EventArgs e) { btStart.IsEnabled = false; btStop.IsEnabled = true; tb.Text = string.Empty; currentOperationTioken = new CancellationTokenSource(); currentOperation = Task.Run(() => HeavyWork(currentOperationTioken.Token)); await currentOperation; btStart.IsEnabled = true; btStop.IsEnabled = false; } В стопе просто вызываем отмену операции private void StopClick(object sender, EventArgs e) { currentOperationTioken.Cancel(); } Наша долгая операция. Как только увидит сигнал об отмене - прекратит работу private void HeavyWork(CancellationToken token) { for (int i = 0; i < 10; i++) { this.Dispatcher.Invoke(() => tb.Text += $"{Environment.NewLine}{i} iteration"); Thread.Sleep(1000); if (token.IsCancellationRequested) return; } } } Запустить окошко можно оч просто void Main() { new MyWindow().ShowDialog(); } Результат
Комментариев нет:
Отправить комментарий