私のアプリケーションは含まListView
バックグラウンドタスクオフ項目が選択されるたびにそのキックを。それが正常に完了したときにバックグラウンドタスクは、UIに関する情報を更新します。
しかし、ユーザーはすぐに別の後に一つの項目をクリックすると、すべてのこれらのタスクを継続し、最後のタスク「勝利」を完了することと関係なく、最後に選択された項目の、UIを更新します。
私に必要なのはどういうわけか、このタスクのみを任意の時点で実行されていることの1つのインスタンスがあることを確認ので、新しいものを開始する前に、以前のすべてのタスクをキャンセルすることです。
ここでは、問題を示してい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
実行されたことを。
私は何とかタスクをスケジュールするか、以前のすべてのタスクをキャンセルするためにサービスにそれらを追加する必要がありますか?
EDIT:
私の実際のアプリケーションでは、ユーザーから項目を選択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
FXのスレッドで起こります。(下の特色)コードのビットだけではない FXのスレッドで実行は内部になりますTask.call()
(クラスがインスタンス化されますときに私はJavaFXの-ランチャースレッドで起こると信じているだけでなく、すぐに割り当てられたフィールド)。
ここで使用して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
コールバック。これは、あるService
ときにのみに開始することができるREADY
状態。あなたは使用することができるrestart()
のではなく、start()
必要に応じて。それは基本的に呼び出すのと同じですcancel()
> - reset()
> - start()
。
1 resuableなりません。むしろ、新しい作成し、それを起動たびに。Task
Service
Task
あなたがキャンセルした場合Service
には、現在実行中のキャンセルTask
がある場合、。にもかかわらずService
、そのためTask
、キャンセルされている実行が実際に停止したという意味ではありません。Javaでは、バックグラウンドタスクをキャンセルすると、タスクの開発者との協力が必要です。
この協力は、実行が停止した場合、定期的にチェックするという形をとります。ノーマルを使用している場合Runnable
か、Callable
これが現在の割り込みステータスを確認する必要になるThread
か、いくつかの使用boolean
フラグ2を。以来Task
拡張FutureTask
を使うこともできますisCancelled()
から継承されたメソッドFuture
のインターフェースを。あなたが使用できない場合はisCancelled()
、何らかの理由で(外コードと呼ばれる、使用していないTask
、あなたが使用してスレッドの中断を確認する...など、):
Thread.interrupted()
- staticメソッド
- 電流だけ確認することができます
Thread
- 現在のスレッドの割り込みステータスをクリアします
Thread.isInterrupted()
- インスタンスメソッド
- いずれかをチェックすることができ
Thread
ますへの参照を持っています - DOESは明らかではない中断状態
あなたは経由で現在のスレッドへの参照を取得することができますThread.currentThread()
。
あなたの背景コードの中には、現在のスレッドが中断された場合には、適切なポイントで確認したいと思い、boolean
フラグが設定されている、またはTask
キャンセルされました。それは持っているなら、あなたは(帰国または例外をスローすることによってのいずれか)、任意の必要なクリーンアップおよび停止実行を行うと思います。
あなたの場合にも、Thread
そのような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
。