如何以正确的方式在 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 上被意外调用。