#c_sharp
На форме имеется две кнопки. По нажатию первой запускается цикл, который может работать довольно большой промежуток времени. Нажатие второй должно приостановить цикл. Каким образом можно добиться такого поведения? Ведь цикл работает в обработчике первой кнопки. Как же циклу дать знать, что была нажата кнопка "Стоп"? Вот пример кода: public void b1(object sender, EventArgs e) { for(int i = 0; i < 1000000; ++i) { ... } } public void b2(object sender, EventArgs e) { ??? }
Ответы
Ответ 1
Для начала: так не пойдёт. Ваш цикл в обработчике события блокирует UI, и не даёт шанса отработать событиям от мыши. Кроме того, и всё приложение будет выглядеть зависшим. Современный метод выполнения фоновых заданий с приостановкой — использование TPL: вам нужен Task и CancellationToken. CancellationTokenSource cts; public async void b1(object sender, EventArgs e) { if (cts != null) return; using (cts = new CancellationTokenSource()) await Calculate(cts.Token); cts = null; } public void b2(object sender, EventArgs e) { if (cts != null) cts.Cancel(); } Task Calculate(CancellationToken ct) { return Task.Run(() => CalculateImpl(ct), ct); } void CalculateImpl(CancellationToken ct) { for (int i = 0; i < 1000000; ++i) { if (ct.IsCancellationRequested) return; // ... } }Ответ 2
Я бы рекомендовал использовать отдельный поток для длительной задачи, чтобы не тормозить поток UI. В данном случае вполне подойдет класс BackgroundWorker: private BackgroundWorker bw; public void b1(object sender, EventArgs e) { bw = new BackgroundWorker { WorkerSupportsCancellation = true }; bw.DoWork += (obj, args) => { BackgroundWorker lbw = (BackgroundWorker)obj; for (int i = 0; i < 1000000 && !lbw.CancellationPending; ++i) { ... } }; bw.RunWorkerAsync(); } public void b2(object sender, EventArgs e) { if (bw != null && bw.IsBusy && !bw.CancellationPending) bw.CancelAsync(); }Ответ 3
Самый простой вариант: Заведите булево поле — флаг, нужно ли продолжать выполнение операции. Выполняйте цикл не в UI потоке (там ничего долго выполняющегося быть не должно). К контролам обращайтесь через Control.Invoke. По нажатию на кнопку Стоп задавайте флагу значение остановки. Булево поле должно быть volatile, иначе компилятор может "оптимизировать" код и избавиться от "избыточной" проверки (ведь внутри цикла значение переменной не меняется). Примерно так: private volatile bool _isRunning; private void buttonStart_Click (object sender, EventArgs e) { Task.Run(() => { _isRunning = true; for (int i = 0; i < 10000; i++) { Invoke((Action)(() => Text = i.ToString())); if (!_isRunning) break; } }); } private void buttonStop_Click (object sender, EventArgs e) { _isRunning = false; }Ответ 4
Можно завести логическое поле, которое проверять на каждой итерации цикла в методе b1 и устанавливать в методе b2. Примерно так: private volatile bool _stop_cycle; public void b1(object sender, EventArgs e) { _stop_cycle = false; for(int i = 0; i < 1000000 && !_stop_cycle; ++i) { ... } } public void b2(object sender, EventArgs e) { _stop_cycle = true; } Здесь разбирается пример работы с переменными, доступными из разных потоков, на более простом примере с циклом while.
Комментариев нет:
Отправить комментарий