Остановка другого потока из EDT (EventQueue)
Поэтому я создал базовый интерфейс свинга с кнопкой остановки, и нажатие на него должно остановить подсчет потока. Когда приложение запускается, экземпляр потока выделяет runnable
класс, который делает цикл подсчета и регистрирует его в консоли. В методе есть runnable
интерфейс, который устанавливает переменную volatile в false, которая должна в основном останавливать поток, и я вызывал ее на кнопке остановки, но почему он не останавливает цикл? Вот мой код
ParentContainer.java
public class ParentContainer extends JFrame implements ActionListener, WindowListener {
private static final long serialVersionUID = 1L;
private JButton btnStop;
private static CountRunnable cr;
public ParentContainer() {
Container cp = getContentPane();
cp.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10));
btnStop = new JButton("Stop Counting");
cp.add(btnStop);
SymAction lSymAction = new SymAction();
btnStop.addActionListener(lSymAction);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Counter");
setSize(200, 90);
setVisible(true);
}
public static void main(String[] args) {
// Run GUI codes in Event-Dispatching thread for thread safety
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new ParentContainer(); // Let the constructor do the job
}
});
cr = new CountRunnable();
Thread t1 = new Thread(cr, "MyRunnable");
t1.start();
}
class SymAction implements ActionListener
{
public void actionPerformed(ActionEvent evt)
{
Object object = evt.getSource();
if (object == btnStop) { btnStop_actionPerformed(evt); }
}
}
void btnStop_actionPerformed(ActionEvent evt)
{
cr.stop();
}
@Override
public void windowActivated(WindowEvent arg0) { }
@Override
public void windowClosed(WindowEvent arg0) { }
@Override
public void windowClosing(WindowEvent arg0) { }
@Override
public void windowDeactivated(WindowEvent arg0) { }
@Override
public void windowDeiconified(WindowEvent arg0) { }
@Override
public void windowIconified(WindowEvent arg0) { }
@Override
public void windowOpened(WindowEvent arg0) { }
@Override
public void actionPerformed(ActionEvent arg0) { }
}
CountRunnable.java
public class CountRunnable implements Runnable {
public static volatile boolean keepRunning;
private int count = 1;
@Override
public void run() {
keepRunning = true;
while (keepRunning)
{
for (int i = 0; i < 100000; ++i) {
System.out.println(count + "");
++count;
// Suspend this thread via sleep() and yield control to other threads.
// Also provide the necessary delay.
try {
Thread.sleep(10); // milliseconds
}
catch (InterruptedException ex) {
}
}
}
}
public void stop()
{
keepRunning = false;
}
}
2 ответа
При выполнении условия остановки важно часто проверять условие. На данный момент у вас есть цикл, который работает некоторое время, и вы видите только результат вашей проверки в конце цикла.
Чтобы убедиться, что цикл завершается в середине итерации, вы можете добавить следующую проверку внутри цикла.
if(!keepRunning) {
break;
}
Есть разные решения, которые вы также можете попробовать. Поскольку этот шаблон остановки потоков часто используется, вы также можете использовать SwingWorker
для вашей подзадачи этот класс предоставляет достаточное количество полезных служебных методов для остановки и для обновления потока GUI, безопасного из вашей подзадачи (так, например, вы можете отображать счетчик в графическом интерфейсе вместо командной строки)
Изменение вашего кода для использования SwingWorker
легко, при расширении SwingWorker
, он ожидает 2 элемента: "конечный результат" и "ожидающий результат".
Пример SwingWorker
:
SwingWorker<String, String> task = new SwingWorker<String, String>(){
private int count = 1;
@Override
public List<Integer> doInBackground() throws Exception {
while (!isCancelled()) {
for (int i = 0; i < 100000; ++i) {
publish(count + "");
++count;
Thread.sleep(10); // milliseconds
}
}
return count + "";
}
@Override
protected void process(List<Integer> chunks) {
gui.setText(chunk.get(chunk.size() - 1));
}
@Override
protected void done() {
try {
gui.setText(this.get());
} catch(InterruptedException | ExecutionException ex) {
ex.printSTackTrace();
}
}
}
task.execute();
// Later:
task.cancel(false);
Обратите внимание, что отмена с помощью false рекомендуется, потому что выполнение этого с true означает, что поток, выполняющий счетчик, будет прерван, в результате чего Thread.sleep() сгенерирует исключение.
Если ваша задача состоит только в подсчете числа, может быть даже проще использовать класс таймера (убедитесь, что вы импортируете правильный), используя этот класс так же просто, как:
int delay = 10; //milliseconds
ActionListener taskPerformer = new ActionListener() {
int count = 0;
public void actionPerformed(ActionEvent evt) {
count++;
gui.setText(count + "");
// System.out.println(count + "");
}
};
Timer task = new Timer(delay, taskPerformer);
task.setCoalesce(false); // SOmetimes, executions can be missed because other programs
task.setRepeating(true);
task.start();
// Later:
task.stop();
Обратите внимание, что с этим таймером вы можете перезапустить его снова, вызвав start()
В отличие от других решений, которые я вам показал.
Потому что сначала он хочет завершить внутренний цикл for
for (int i = 0; i < 100000; ++i)
перед переходом к следующей итерации внешнего цикла while для проверки keepRunning
снова логическое.
Просто удалите цикл for.