取消 javax.swing 框架中的长任务
Cancel long task in javax.swing framework
我有一个带有 java.swing 组件、ActionListeners 和 SwingWorker 的 GUI,用于在单独的线程中执行更多代码。我明白一个SwingWorker只能创建一次,不能终止,只能取消。此外,我认为使用其方法 isCancelled() 检查 SwingWorker 状态是一种很好的做法,以防万一退出 doInBackground() 方法并相应地在 done() 方法中做出反应。例如,如果您在 doInBackground() 方法中有一个循环并且可以在每次迭代时测试 isCancelled() ,这就可以正常工作。
但是如何才能真正 break/terminate 在 doInBackground() 方法中执行的长任务,例如读取大型 csv (>1GB) 或从另一个 [=24= 调用进程密集型方法]?为了说明我的问题,我构建了一个简单的程序,当你选择一个大的输入 csv 时,它会显示我的问题。停止按钮在计数器循环中工作正常,但不会终止 csv 导入。
我怎样才能真正 break/terminate 一个持久的过程?如果这对于 SwingWorker 是不可能的,我将如何使用线程来做到这一点? thread.interrupt() 有可能吗?我将如何在我的示例中实现它?
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import com.opencsv.CSVReader;
public class MinimalSwing extends JFrame {
// fields
private JButton fileButton, startButton, stopButton;
private JLabel displayLabel;
private File csvIn;
private SwingWorkerClass swingWorker;
// constructor
public MinimalSwing() {
// set GUI-window properties
setSize(300, 200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocation(200, 200);
setTitle("MinimalSwing");
setLayout(new BorderLayout(9, 9));
setResizable(false);
// set components
fileButton = new JButton("Choose File");
fileButton.addActionListener(new ButtonActionListener());
getContentPane().add("North", fileButton);
startButton = new JButton("Start");
startButton.setEnabled(false);
startButton.addActionListener(new ButtonActionListener());
getContentPane().add("West", startButton);
stopButton = new JButton("Stop");
stopButton.setEnabled(false);
stopButton.addActionListener(new ButtonActionListener());
getContentPane().add("East", stopButton);
displayLabel = new JLabel("Status...");
getContentPane().add("South", displayLabel);
}
// csvFileChooser for import
private File getCsv() {
JFileChooser fc = new JFileChooser();
int openDialogReturnVal = fc.showOpenDialog(null);
if(openDialogReturnVal != JFileChooser.APPROVE_OPTION){
System.out.println("ERROR: Invalid file choice.");
}
return fc.getSelectedFile();
}
// csvImporter
private class CsvImporter {
public void readCsv(File file) throws IOException {
CSVReader reader = new CSVReader(new FileReader(file));
String [] nextLine;
reader.readNext();
while ((nextLine = reader.readNext()) != null) {
displayLabel.setText("..still reading");
}
reader.close();
displayLabel.setText("..actually done.");
}
}
// ActionListener
private class ButtonActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == fileButton) {
csvIn = getCsv();
if(csvIn != null) {
startButton.setEnabled(true);
stopButton.setEnabled(true);
}
}
else if(e.getSource() == startButton) {
fileButton.setEnabled(false);
startButton.setEnabled(false);
stopButton.setEnabled(true);
swingWorker = new SwingWorkerClass();
swingWorker.execute();
}
else {
fileButton.setEnabled(true);
startButton.setEnabled(true);
stopButton.setEnabled(false);
swingWorker.cancel(true);
}
}
}
// swingWorker to interact with further program
private class SwingWorkerClass extends SwingWorker<Boolean, Void> {
@Override
protected Boolean doInBackground() throws Exception {
long t0 = System.currentTimeMillis();
displayLabel.setText("starting execution...");
displayLabel.setText("..importing csv");
CsvImporter csvImporter = new CsvImporter();
csvImporter.readCsv(csvIn);
if(isCancelled()) return false; // this cancels after the import, but I want to cancel during the import...
long t1 = System.currentTimeMillis();
displayLabel.setText("csv imported in " + String.format("%,d", t1 - t0) + " ms");
for(short i=1; i<=10; i++) {
if(isCancelled()) return false; // this works fine as it is called every second
TimeUnit.SECONDS.sleep(1);
displayLabel.setText("counter: " + i);
}
return true;
}
@Override
public void done() {
fileButton.setEnabled(true);
startButton.setEnabled(true);
stopButton.setEnabled(false);
if(isCancelled()) {
displayLabel.setText("Execution cancelled.");
}
else {
displayLabel.setText("Execution succeeded.");
}
}
}
// main
public static void main(String[] args) throws URISyntaxException, IOException {
// launch gui
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
MinimalSwing frame = new MinimalSwing();
frame.setVisible(true);
}
catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
您可以让您的 CSVImporter 扩展 SwingWorker 而不是再拥有一个 class SwingWorkerClass。这样您就可以获得更多控制权并取消导入任务。
类似下面的内容。
private class CsvImporter extends SwingWorker<Boolean, Void> {
public boolean readCsv(File file) throws IOException {
CSVReader reader = new CSVReader(new FileReader(file));
String[] nextLine;
reader.readNext();
while ((nextLine = reader.readNext()) != null) {
displayLabel.setText("..still reading");
if (isCancelled())
return false; // this cancels after the import, but I want
// to cancel during the import...
}
reader.close();
displayLabel.setText("..actually done.");
return true; // read complete
}
@Override
protected Boolean doInBackground() throws Exception {
long t0 = System.currentTimeMillis();
displayLabel.setText("starting execution...");
displayLabel.setText("..importing csv");
CsvImporter csvImporter = new CsvImporter();
boolean readStatus = csvImporter.readCsv(csvIn);
if (readStatus) {
long t1 = System.currentTimeMillis();
displayLabel.setText("csv imported in " + String.format("%,d", t1 - t0) + " ms");
for (short i = 1; i <= 10; i++) {
if (isCancelled())
return false; // this works fine as it is called every second
TimeUnit.SECONDS.sleep(1);
displayLabel.setText("counter: " + i);
}
}
return readStatus;
}
}
我有一个带有 java.swing 组件、ActionListeners 和 SwingWorker 的 GUI,用于在单独的线程中执行更多代码。我明白一个SwingWorker只能创建一次,不能终止,只能取消。此外,我认为使用其方法 isCancelled() 检查 SwingWorker 状态是一种很好的做法,以防万一退出 doInBackground() 方法并相应地在 done() 方法中做出反应。例如,如果您在 doInBackground() 方法中有一个循环并且可以在每次迭代时测试 isCancelled() ,这就可以正常工作。
但是如何才能真正 break/terminate 在 doInBackground() 方法中执行的长任务,例如读取大型 csv (>1GB) 或从另一个 [=24= 调用进程密集型方法]?为了说明我的问题,我构建了一个简单的程序,当你选择一个大的输入 csv 时,它会显示我的问题。停止按钮在计数器循环中工作正常,但不会终止 csv 导入。
我怎样才能真正 break/terminate 一个持久的过程?如果这对于 SwingWorker 是不可能的,我将如何使用线程来做到这一点? thread.interrupt() 有可能吗?我将如何在我的示例中实现它?
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import com.opencsv.CSVReader;
public class MinimalSwing extends JFrame {
// fields
private JButton fileButton, startButton, stopButton;
private JLabel displayLabel;
private File csvIn;
private SwingWorkerClass swingWorker;
// constructor
public MinimalSwing() {
// set GUI-window properties
setSize(300, 200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocation(200, 200);
setTitle("MinimalSwing");
setLayout(new BorderLayout(9, 9));
setResizable(false);
// set components
fileButton = new JButton("Choose File");
fileButton.addActionListener(new ButtonActionListener());
getContentPane().add("North", fileButton);
startButton = new JButton("Start");
startButton.setEnabled(false);
startButton.addActionListener(new ButtonActionListener());
getContentPane().add("West", startButton);
stopButton = new JButton("Stop");
stopButton.setEnabled(false);
stopButton.addActionListener(new ButtonActionListener());
getContentPane().add("East", stopButton);
displayLabel = new JLabel("Status...");
getContentPane().add("South", displayLabel);
}
// csvFileChooser for import
private File getCsv() {
JFileChooser fc = new JFileChooser();
int openDialogReturnVal = fc.showOpenDialog(null);
if(openDialogReturnVal != JFileChooser.APPROVE_OPTION){
System.out.println("ERROR: Invalid file choice.");
}
return fc.getSelectedFile();
}
// csvImporter
private class CsvImporter {
public void readCsv(File file) throws IOException {
CSVReader reader = new CSVReader(new FileReader(file));
String [] nextLine;
reader.readNext();
while ((nextLine = reader.readNext()) != null) {
displayLabel.setText("..still reading");
}
reader.close();
displayLabel.setText("..actually done.");
}
}
// ActionListener
private class ButtonActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == fileButton) {
csvIn = getCsv();
if(csvIn != null) {
startButton.setEnabled(true);
stopButton.setEnabled(true);
}
}
else if(e.getSource() == startButton) {
fileButton.setEnabled(false);
startButton.setEnabled(false);
stopButton.setEnabled(true);
swingWorker = new SwingWorkerClass();
swingWorker.execute();
}
else {
fileButton.setEnabled(true);
startButton.setEnabled(true);
stopButton.setEnabled(false);
swingWorker.cancel(true);
}
}
}
// swingWorker to interact with further program
private class SwingWorkerClass extends SwingWorker<Boolean, Void> {
@Override
protected Boolean doInBackground() throws Exception {
long t0 = System.currentTimeMillis();
displayLabel.setText("starting execution...");
displayLabel.setText("..importing csv");
CsvImporter csvImporter = new CsvImporter();
csvImporter.readCsv(csvIn);
if(isCancelled()) return false; // this cancels after the import, but I want to cancel during the import...
long t1 = System.currentTimeMillis();
displayLabel.setText("csv imported in " + String.format("%,d", t1 - t0) + " ms");
for(short i=1; i<=10; i++) {
if(isCancelled()) return false; // this works fine as it is called every second
TimeUnit.SECONDS.sleep(1);
displayLabel.setText("counter: " + i);
}
return true;
}
@Override
public void done() {
fileButton.setEnabled(true);
startButton.setEnabled(true);
stopButton.setEnabled(false);
if(isCancelled()) {
displayLabel.setText("Execution cancelled.");
}
else {
displayLabel.setText("Execution succeeded.");
}
}
}
// main
public static void main(String[] args) throws URISyntaxException, IOException {
// launch gui
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
MinimalSwing frame = new MinimalSwing();
frame.setVisible(true);
}
catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
您可以让您的 CSVImporter 扩展 SwingWorker 而不是再拥有一个 class SwingWorkerClass。这样您就可以获得更多控制权并取消导入任务。
类似下面的内容。
private class CsvImporter extends SwingWorker<Boolean, Void> {
public boolean readCsv(File file) throws IOException {
CSVReader reader = new CSVReader(new FileReader(file));
String[] nextLine;
reader.readNext();
while ((nextLine = reader.readNext()) != null) {
displayLabel.setText("..still reading");
if (isCancelled())
return false; // this cancels after the import, but I want
// to cancel during the import...
}
reader.close();
displayLabel.setText("..actually done.");
return true; // read complete
}
@Override
protected Boolean doInBackground() throws Exception {
long t0 = System.currentTimeMillis();
displayLabel.setText("starting execution...");
displayLabel.setText("..importing csv");
CsvImporter csvImporter = new CsvImporter();
boolean readStatus = csvImporter.readCsv(csvIn);
if (readStatus) {
long t1 = System.currentTimeMillis();
displayLabel.setText("csv imported in " + String.format("%,d", t1 - t0) + " ms");
for (short i = 1; i <= 10; i++) {
if (isCancelled())
return false; // this works fine as it is called every second
TimeUnit.SECONDS.sleep(1);
displayLabel.setText("counter: " + i);
}
}
return readStatus;
}
}