如何以正确的方式在 JavaFX 中实现启动和暂停功能?
How to implement start and pause functions in JavaFX the correct way?
我正在创建一个 JavaFX 来扫描您的收件箱。
有2个按钮,一个开始扫描,一个暂停扫描。
为了实现这一点,我创建了一个新线程,将其传递到调用 scanInbox() 函数的可运行对象中。
但是,当我按下暂停按钮调用 thread.wait() 时,它似乎卡住了。
在这里实现此功能的最佳方式是什么?
public class WebsiteOverviewController {
@FXML
private TableView<Website> deleteTable;
@FXML
private TableColumn<Website, String> deleteColumn;
@FXML
private TableView<Website> keepTable;
@FXML
private TableColumn<Website, String> keepColumn;
@FXML
private JFXButton scanButton;
@FXML
private JFXButton pauseButton;
private BooleanProperty isScanning = new SimpleBooleanProperty(false);
private MainApp mainApp;
private FilteredList<Website> keepData;
private FilteredList<Website> deleteData;
Task<Void> task;
Thread thread;
public WebsiteOverviewController() {
}
@FXML
public void initialize() {
deleteColumn.setCellValueFactory(cellData -> cellData.getValue().websiteProperty());
keepColumn.setCellValueFactory(cellData -> cellData.getValue().websiteProperty());
scanButton.visibleProperty().bind(isScanning.not());
pauseButton.visibleProperty().bind(isScanning);
}
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
keepData = new FilteredList<>(mainApp.getWebsiteData(), p -> p.getKeep());
deleteData = new FilteredList<>(mainApp.getWebsiteData(), p -> !p.getKeep());
deleteTable.setItems(deleteData);
keepTable.setItems(keepData);
}
@FXML
public void handleScanInbox() {
isScanning.set(true);
thread = new Thread(new Runnable() {
@Override
public void run() {
mainApp.handleScanInbox();
}
});
thread.start();
}
@FXML
public void handlePauseScanInbox() {
isScanning.set(false);
try {
synchronized(thread) {
thread.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
您可以使用 AtomicBoolean 实现它,在暂停时将其设置为真,并在您的 mainApp.handleScanInbox() 方法中检查它。
您可以检查它是否在每次迭代、每 10 次迭代或方法的每个 运行 暂停,具体取决于您的要求
AtomicBoolean paused = new AtomicBoolean(false);
@FXML
public void handlePauseScanInbox() {
paused.compareAndSet(false,true);
}
//mainApp.handleScanInbox();
public void handleScanInbox(AtomicBoolean paused){
for(/* your entire inbox*/){
while(paused.get()){
TimeUnit.SECONDS.sleep(3);
}
}
}
Alex 的解决方案工作正常,但如果您仍想使用较低级别的线程管理(等待通知),您可以这样做:
public class WebsiteOverviewController {
@FXML
public void handleScanInbox() {
isScanning.set(true);
thread = new Thread(mainApp::handleScanInbox);
thread.start();
}
@FXML
public void handlePauseScanInbox() {
isScanning.set(false);
mainApp.pause();
}
// Another handler for resuming...
}
public class MainApp {
private final AtomicBoolean paused = new AtomicBoolean(false);
public void handleScanInbox() {
for (int i = 0; i < numberOfItems; i++) { // This could be a while loop
synchronized (paused) {
while (paused.get()) { // Using while so that it could re-wait in the case that the object was falsely notified
try {
pause.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// Do whatever you need to do
}
}
public void pause() {
pause.compareAndSet(false, true);
}
public void resume() {
synchronized (paused) {
if (paused.get()) {
paused.set(false);
paused.notify();
}
}
}
}
更新
如果您希望能够调用特定的方法在暂停和恢复之间切换,您可以为此添加另一个方法:
// In MainApp
public void togglePauseResume() {
synchronized (paused) {
if (paused.get()) {
paused.set(false);
paused.notify();
}
else {
paused.set(true); // You don't need compareAndSet() because paused object has been used for synchronization (i.e. locked)
}
}
}
无论如何,你应该尽量避免这种情况:
@FXML
public void handleButton() {
if (mainApp.isPaused()) { // You added a getter for paused
mainApp.pause();
}
else {
mainApp.resume();
}
}
这是因为 MainApp.paused
可能会在 getter 和 pause()
之间发生变化(即竞争条件)。
更新 2
如果您只想对线程使用单一方法 start/resume,如果线程是 null
,您可以简单地创建线程,否则调用 resume()
。
@FXML
public void handleScanInbox() {
isScanning.set(true); // Not sure if you still need this
if (thread == null) {
thread = new Thread(mainApp::handleScanInbox);
thread.start();
}
else if (thread.isAlive()) {
mainApp.resume();
}
}
我也将 if (paused.get())
更改为 while (paused.get())
,以防 notify()
在 paused
上被意外调用。
我正在创建一个 JavaFX 来扫描您的收件箱。
有2个按钮,一个开始扫描,一个暂停扫描。
为了实现这一点,我创建了一个新线程,将其传递到调用 scanInbox() 函数的可运行对象中。
但是,当我按下暂停按钮调用 thread.wait() 时,它似乎卡住了。
在这里实现此功能的最佳方式是什么?
public class WebsiteOverviewController {
@FXML
private TableView<Website> deleteTable;
@FXML
private TableColumn<Website, String> deleteColumn;
@FXML
private TableView<Website> keepTable;
@FXML
private TableColumn<Website, String> keepColumn;
@FXML
private JFXButton scanButton;
@FXML
private JFXButton pauseButton;
private BooleanProperty isScanning = new SimpleBooleanProperty(false);
private MainApp mainApp;
private FilteredList<Website> keepData;
private FilteredList<Website> deleteData;
Task<Void> task;
Thread thread;
public WebsiteOverviewController() {
}
@FXML
public void initialize() {
deleteColumn.setCellValueFactory(cellData -> cellData.getValue().websiteProperty());
keepColumn.setCellValueFactory(cellData -> cellData.getValue().websiteProperty());
scanButton.visibleProperty().bind(isScanning.not());
pauseButton.visibleProperty().bind(isScanning);
}
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
keepData = new FilteredList<>(mainApp.getWebsiteData(), p -> p.getKeep());
deleteData = new FilteredList<>(mainApp.getWebsiteData(), p -> !p.getKeep());
deleteTable.setItems(deleteData);
keepTable.setItems(keepData);
}
@FXML
public void handleScanInbox() {
isScanning.set(true);
thread = new Thread(new Runnable() {
@Override
public void run() {
mainApp.handleScanInbox();
}
});
thread.start();
}
@FXML
public void handlePauseScanInbox() {
isScanning.set(false);
try {
synchronized(thread) {
thread.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
您可以使用 AtomicBoolean 实现它,在暂停时将其设置为真,并在您的 mainApp.handleScanInbox() 方法中检查它。 您可以检查它是否在每次迭代、每 10 次迭代或方法的每个 运行 暂停,具体取决于您的要求
AtomicBoolean paused = new AtomicBoolean(false);
@FXML
public void handlePauseScanInbox() {
paused.compareAndSet(false,true);
}
//mainApp.handleScanInbox();
public void handleScanInbox(AtomicBoolean paused){
for(/* your entire inbox*/){
while(paused.get()){
TimeUnit.SECONDS.sleep(3);
}
}
}
Alex 的解决方案工作正常,但如果您仍想使用较低级别的线程管理(等待通知),您可以这样做:
public class WebsiteOverviewController {
@FXML
public void handleScanInbox() {
isScanning.set(true);
thread = new Thread(mainApp::handleScanInbox);
thread.start();
}
@FXML
public void handlePauseScanInbox() {
isScanning.set(false);
mainApp.pause();
}
// Another handler for resuming...
}
public class MainApp {
private final AtomicBoolean paused = new AtomicBoolean(false);
public void handleScanInbox() {
for (int i = 0; i < numberOfItems; i++) { // This could be a while loop
synchronized (paused) {
while (paused.get()) { // Using while so that it could re-wait in the case that the object was falsely notified
try {
pause.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// Do whatever you need to do
}
}
public void pause() {
pause.compareAndSet(false, true);
}
public void resume() {
synchronized (paused) {
if (paused.get()) {
paused.set(false);
paused.notify();
}
}
}
}
更新
如果您希望能够调用特定的方法在暂停和恢复之间切换,您可以为此添加另一个方法:
// In MainApp
public void togglePauseResume() {
synchronized (paused) {
if (paused.get()) {
paused.set(false);
paused.notify();
}
else {
paused.set(true); // You don't need compareAndSet() because paused object has been used for synchronization (i.e. locked)
}
}
}
无论如何,你应该尽量避免这种情况:
@FXML
public void handleButton() {
if (mainApp.isPaused()) { // You added a getter for paused
mainApp.pause();
}
else {
mainApp.resume();
}
}
这是因为 MainApp.paused
可能会在 getter 和 pause()
之间发生变化(即竞争条件)。
更新 2
如果您只想对线程使用单一方法 start/resume,如果线程是 null
,您可以简单地创建线程,否则调用 resume()
。
@FXML
public void handleScanInbox() {
isScanning.set(true); // Not sure if you still need this
if (thread == null) {
thread = new Thread(mainApp::handleScanInbox);
thread.start();
}
else if (thread.isAlive()) {
mainApp.resume();
}
}
我也将 if (paused.get())
更改为 while (paused.get())
,以防 notify()
在 paused
上被意外调用。