내 응용 프로그램이 포함되어 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
. 마지막 성공적인 실행이 당신에게 반환됩니다이 보장. 이전에 취소하더라도 Task
s는 여전히 결과를 반환은 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()
취소 후와에 onSucceeded
와 onFailed
콜백. a가 있기 때문입니다 Service
경우에만 시작할 수 있습니다 READY
상태 . 당신은 사용할 수 있습니다 restart()
보다는 start()
필요한 경우. 그것은 호출 기본적으로 동일합니다 cancel()
-> reset()
-> start()
.
1 는 Task
resuable가되지 않습니다. 오히려,이 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
취소되었습니다. 이 경우 당신은 (반환하거나 예외를 던져 하나) 필요한 정리 및 정지 실행을 수행 할 것입니다.
귀하의 경우에도, Thread
IS 일부 인터럽트 동작을 기다리고, 같은 IO 차단으로, 다음은 발생합니다 InterruptedException
때 중단. 당신의 MVCE에서는 사용하는 Thread.sleep
인터럽트이다; 당신이 언급 한 예외가 발생합니다이 방법을 취소 호출 할 때 의미한다.
내가 필요한까지 모든 청소를 의미 위 "정리"말할 때 백그라운드에서 당신이 백그라운드 스레드에서 아직도 이후를. (예 : UI를 업데이트하는 등) FX 스레드에 아무것도를 정리해야하는 경우 다음 당신은 사용할 수 onCancelled
의 속성을 Service
.
코드에서 당신은 또한 내가 보호 방법을 사용하여 볼 수 있습니다 위의 succeeded()
와 cancelled()
. 둘 다 Task
와 Service
(다양한뿐만 아니라 다른 사람이 방법 제공 Worker.State
들) 그리고 그들은 항상 FX 스레드에서 호출됩니다. 그러나, 같은 문서를 읽기 ScheduledService
이러한 방법 중 일부 슈퍼 구현을 호출하도록 요구합니다.
2 사용하는 경우 boolean
플래그는 반드시 업데이트가 다른 스레드에 볼 수 있습니다합니다. 당신은 그것을 만드는이 작업을 수행 할 수 있습니다 volatile
, 그것을 동기화, 또는를 사용하여 java.util.concurrent.atomic.AtomicBoolean
.