#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(); } Результат
Комментариев нет:
Отправить комментарий