Страницы

Поиск по вопросам

среда, 29 января 2020 г.

Остановка работы потока по нажатию кнопки

#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) { //через него будем оповещать о ходе выполнения задачи Progress progess = 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(); } Результат

Комментариев нет:

Отправить комментарий