首页 文章

如何在JavaFX中以正确的方式实现启动和暂停功能?

提问于
浏览 845
-1

我正在创建一个扫描收件箱的JavaFX .

有2个按钮,一个用于开始扫描,另一个用于暂停扫描 .

为了实现这一点,我创建了一个新线程,在一个runnable中传递它,调用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();
        }
    }


}

2 回答

  • 2

    您可以使用AtomicBoolean实现它,在暂停时将其设置为true,并在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);
                }
            }
        }
    
  • 1

    Alex的解决方案工作正常,但如果您仍想使用较低级别的线程管理(wait-notify),那么您可以这样做:

    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

    如果您只想使用单个方法来启动/恢复该线程,则只需创建该线程(如果它是 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 .

相关问题