작업의 새로운 인스턴스가 시작되면 어떻게 작업을 취소하려면?

제퍼 :

내 응용 프로그램이 포함되어 ListView백그라운드 작업 오프 항목이 선택 될 때마다 그 발 차기. 이 성공적으로 완료 될 때 백그라운드 작업 후 UI에 대한 정보를 업데이트합니다.

사용자가 신속하게 다른 후 하나 개의 항목을 클릭 할 때 그러나,이 모든 작업은 계속하고 마지막 작업에 관계없이 최종 선정 된 아이템의 UI를 "승리"를 완료하고 업데이트 할 수 있습니다.

내가 필요한 것은 어떻게 든이 작업은 주어진 시간에 실행 그것의 하나 개의 인스턴스가 보장 그래서 새로운 하나를 시작하기 전에 모든 이전 작업을 취소하는 것입니다.

여기에 문제를 보여주는 MCVE입니다 :

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class taskRace  extends Application {

    private final ListView<String> listView = new ListView<>();
    private final Label label = new Label("Nothing selected");
    private String labelValue;

    public static void main(String[] args) {

        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {

        // Simple UI
        VBox root = new VBox(5);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));
        root.getChildren().addAll(listView, label);

        // Populate the ListView
        listView.getItems().addAll(
                "One", "Two", "Three", "Four", "Five"
        );

        // Add listener to the ListView to start the task whenever an item is selected
        listView.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {

            if (newValue != null) {

                // Create the background task
                Task task = new Task() {
                    @Override
                    protected Object call() throws Exception {

                        String selectedItem = listView.getSelectionModel().getSelectedItem();

                        // Do long-running task (takes random time)
                        long waitTime = (long)(Math.random() * 15000);
                        System.out.println("Waiting " + waitTime);
                        Thread.sleep(waitTime);
                        labelValue = "You have selected item: " + selectedItem ;
                        return null;
                    }
                };

                // Update the label when the task is completed
                task.setOnSucceeded(event ->{
                    label.setText(labelValue);
                });

                new Thread(task).start();
            }

        });

        stage.setScene(new Scene(root));
        stage.show();

    }
}

임의의 순서로 여러 항목을 클릭하면, 결과는 예측할 수 없다. 레이블이 마지막의 결과를 표시하도록 업데이트 할 수 있도록 내가 필요한 것은 Task실행 된 것을.

어떻게 든 작업을 예약하거나 모든 이전 작업을 취소하기 위해 서비스에 추가해야합니까?

편집하다:

내 실제 응용 프로그램에서 사용자가에서 항목을 선택 ListView하고 백그라운드 작업은 데이터베이스 (복잡한 읽어 SELECT문) 해당 항목과 관련된 모든 정보를 얻을 수 있습니다. 그 세부 사항은 다음 응용 프로그램에 표시됩니다.

발생되는 문제는 사용자가 항목을 선택하지만 그 선택을 변경하는 경우, 데이터는 전혀 다른 항목이 현재 선택되는 경우에도, 선택된 첫 번째 항목이 될 수있는 응용 프로그램에 표시 복귀된다.

제에서 리턴 데이터 (예 : 원하지 않는) 선택이 완전히 폐기 될 수있다.

양배추 :

귀하의 요구 사항은, 이 답변이 언급하는 사용을위한 완벽한 이유처럼 보인다 Service. A는 Service당신이 실행할 수있는 하나의 Task 재사용에 주어진 시간을 1 개 방식. 당신이 취소 할 때 Service,를 통해 Service.cancel(), 그것은 기본이 취소됩니다 Task. Service또한 자체를 추적 Task 당신을 위해 당신이 목록 어딘가에 보관 할 필요가 없습니다.

당신이 원하는 것 무엇을 당신의 MVCE를 사용하면 만드는 것입니다 Service당신을 래핑 Task. 때마다 사용자가에 새로운 항목을 선택 ListView하면 취소 것 Service를 다시 시작 필요한 상태를 업데이트하고 Service. 그런 다음 사용하십시오 Service.setOnSucceeded받는 결과를 설정하는 콜백을 Label. 마지막 성공적인 실행이 당신에게 반환됩니다이 보장. 이전에 취소하더라도 Tasks는 여전히 결과를 반환은 Service그들을 무시합니다.

당신은 또한 (당신의 MVCE 적어도) 외부 동기화에 대해 걱정할 필요가 없습니다. 모든 작업 거래를 시작, 취소하고,이 관찰과 그 Service외환 스레드에서 발생합니다. 코드의 유일한 비트 (아래 기능) 하지 외환 스레드에서 실행 안에있을 것이다 Task.call()(물론, 및 클래스가 인스턴스화됩니다 때 즉시 할당 필드는 나는 자바 FX-실행기 스레드에서 발생 믿는).

여기에 사용하여 MVCE의 수정 된 버전입니다 Service:

import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    private final ListView<String> listView = new ListView<>();
    private final Label label = new Label("Nothing selected");

    private final QueryService service = new QueryService();

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        service.setOnSucceeded(wse -> {
            label.setText(service.getValue());
            service.reset();
        });
        service.setOnFailed(wse -> {
            // you could also show an Alert to the user here
            service.getException().printStackTrace();
            service.reset();
        });

        // Simple UI
        VBox root = new VBox(5);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));
        root.getChildren().addAll(listView, label);

        // Populate the ListView
        listView.getItems().addAll(
                "One", "Two", "Three", "Four", "Five"
        );

        listView.getSelectionModel().selectedItemProperty().addListener((observableValue, oldValue, newValue) -> {
            if (service.isRunning()) {
                service.cancel();
                service.reset();
            }
            service.setSelected(newValue);
            service.start();
        });

        stage.setScene(new Scene(root));
        stage.show();

    }

    private static class QueryService extends Service<String> {

        // Field representing a JavaFX property
        private String selected;

        private void setSelected(String selected) {
            this.selected = selected;
        }

        @Override
        protected Task<String> createTask() {
            return new Task<>() {

                // Task state should be immutable/encapsulated
                private final String selectedCopy = selected;

                @Override
                protected String call() throws Exception {
                    try {
                        long waitTime = (long) (Math.random() * 15_000);
                        System.out.println("Waiting " + waitTime);
                        Thread.sleep(waitTime);
                        return "You have selected item: " + selectedCopy;
                    } catch (InterruptedException ex) {
                        System.out.println("Task interrupted!");
                        throw ex;
                    }
                }

            };
        }

        @Override
        protected void succeeded() {
            System.out.println("Service succeeded.");
        }

        @Override
        protected void cancelled() {
            System.out.println("Service cancelled.");
        }

    }
}

당신이 호출 할 때 Service.start()그것은 생성 Task하고 실행하는 그것을 현재 사용 Executor의에 포함 된 실행 프로그램 속성을 . 속성이 포함되어있는 경우 null다음 몇 가지 지정, 기본값을 사용합니다 Executor(데몬 스레드를 사용하여).

이상은, 당신은 내가 전화를 참조 reset()취소 후와에 onSucceededonFailed콜백. a가 있기 때문입니다 Service경우에만 시작할 수 있습니다 READY 상태 . 당신은 사용할 수 있습니다 restart()보다는 start()필요한 경우. 그것은 호출 기본적으로 동일합니다 cancel()-> reset()-> start().

1 Taskresuable가되지 않습니다. 오히려,이 Service새로운 생성 Task이 시작 될 때마다.


당신이 취소되면 Service그것은 현재 실행 취소 Task가있는 경우. 심지어 불구하고 Service, 따라서 Task, 취소 된 사실을 중지 한 그 실행을 의미하지 않는다 . 자바에서 백그라운드 작업을 취소하는 것을 작업의 개발자와의 협력을 필요로한다.

이 협력은 실행을 중지해야하는 경우 주기적으로 확인하는 형식을 취합니다. 정상를 사용하는 경우 Runnable또는 Callable이 현재의 중단 상태를 확인 필요 Thread또는 일부 사용 boolean플래그 2 . 이후 Task확장 FutureTask당신은 또한 사용할 수 있습니다 isCancelled()으로부터 상속 방법 Future인터페이스를. 당신이 사용할 수없는 경우 isCancelled()어떤 이유로 (외부 코드라고하는을 사용하지 않는 Task한 다음 사용하여 스레드 중단 확인 ... 등)

  • Thread.interrupted()
    • static 메소드
    • 단지 현재 확인하실 수 있습니다 Thread
    • 현재 스레드의 중단 상태를 지 웁니다
  • Thread.isInterrupted()
    • 인스턴스 방법
    • 어떤을 확인하실 수 있습니다 Thread당신에 대한 참조가
    • 명확하지 중단 상태를합니까

당신은을 통해 현재의 thread에 대한 참조를 얻을 수 있습니다Thread.currentThread() .

당신의 배경 코드 내부 현재 스레드가 중단 된 경우, 적절한 지점에서 확인 할 거라고는 boolean플래그가 설정되어있는, 또는이 Task취소되었습니다. 이 경우 당신은 (반환하거나 예외를 던져 하나) 필요한 정리 및 정지 실행을 수행 할 것입니다.

귀하의 경우에도, ThreadIS 일부 인터럽트 동작을 기다리고, 같은 IO 차단으로, 다음은 발생합니다 InterruptedException때 중단. 당신의 MVCE에서는 사용하는 Thread.sleep인터럽트이다; 당신이 언급 한 예외가 발생합니다이 방법을 취소 호출 할 때 의미한다.

내가 필요한까지 모든 청소를 의미 위 "정리"말할 때 백그라운드에서 당신이 백그라운드 스레드에서 아직도 이후를. (예 : UI를 업데이트하는 등) FX 스레드에 아무것도를 정리해야하는 경우 다음 당신은 사용할 수 onCancelled의 속성을 Service.

코드에서 당신은 또한 내가 보호 방법을 사용하여 볼 수 있습니다 위의 succeeded()cancelled(). 둘 다 TaskService(다양한뿐만 아니라 다른 사람이 방법 제공 Worker.State들) 그리고 그들은 항상 FX 스레드에서 호출됩니다. 그러나, 같은 문서를 읽기 ScheduledService이러한 방법 중 일부 슈퍼 구현을 호출하도록 요구합니다.

2 사용하는 경우 boolean플래그는 반드시 업데이트가 다른 스레드에 볼 수 있습니다합니다. 당신은 그것을 만드는이 작업을 수행 할 수 있습니다 volatile, 그것을 동기화, 또는를 사용하여 java.util.concurrent.atomic.AtomicBoolean.

추천

출처http://43.154.161.224:23101/article/api/json?id=180514&siteId=1