is there an option to cancel a task in ScheduledExecutorService? - java

I am writing an application on javaFX and as a part of my view(in MVVM architecture) I am using an FXML Label for the status of my application to present for the client(for example: if a file was uploaded successfully or not).
I want to clean this status label a few seconds after an update was made.
I used this code to run a thread to do so but the problem occurs when more than one update was made before the previous finished its job.
I thought of canceling any previous threads in the thread-pool before executing a new thread but I didn't find a way to do so.
public class MainWindowController implements Observer {
ViewModel vm;
ScheduledExecutorService scheduledExecutorService;
Stage stage;
#FXML
Label appStatus;
public void loadProperties(){
FileChooser fc = new FileChooser();
fc.setTitle("Load Project Properties");
fc.setInitialDirectory(new File("./resources"));
FileChooser.ExtensionFilter extensionFilter = new FileChooser.ExtensionFilter(
"XML Files (*.xml)", "*.xml");
fc.getExtensionFilters().add(extensionFilter);
File chosenFile = fc.showOpenDialog(stage);
//CONTINUE HERE
if(chosenFile==null){
appStatus.setTextFill(Color.RED);
vm.appStat.setValue("Failed to load resource");
}
else{
vm.setAppProperties(chosenFile.getAbsolutePath());
}
cleanStatusBox();
}
public void cleanStatusBox(){
scheduledExecutorService.schedule(new Runnable() {
#Override
public void run() {
Platform.runLater(()->vm.appStat.setValue(""));
}
},10000, TimeUnit.MILLISECONDS);
}
public void setViewModel(ViewModel vm) {
this.vm = vm;
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
appStatus.textProperty().bind(vm.appStat);
}
}

A ScheduledExecutorService (and using background threads in general) is far too heavy-handed an approach for this.
Instead, consider using a PauseTransition, which once started will execute its onFinished handler on the JavaFX Application Thread after the specified time. The playFromStart() method "restarts" the pause, enabling multiple updates without conflict.
public class MainWindowController implements Observer {
ViewModel vm;
PauseTransition clearLabelPause;
Stage stage;
#FXML
Label appStatus;
public void loadProperties(){
FileChooser fc = new FileChooser();
fc.setTitle("Load Project Properties");
fc.setInitialDirectory(new File("./resources"));
FileChooser.ExtensionFilter extensionFilter = new FileChooser.ExtensionFilter(
"XML Files (*.xml)", "*.xml");
fc.getExtensionFilters().add(extensionFilter);
File chosenFile = fc.showOpenDialog(stage);
//CONTINUE HERE
if(chosenFile==null){
appStatus.setTextFill(Color.RED);
vm.appStat.setValue("Failed to load resource");
}
else{
vm.setAppProperties(chosenFile.getAbsolutePath());
}
clearLabelPause.playFromStart();
}
public void setViewModel(ViewModel vm) {
this.vm = vm;
clearLabelPause = new PauseTransition(Duration.seconds(10));
clearLabelPause.setOnFinished(e -> vm.appStat.setValue(""));
appStatus.textProperty().bind(vm.appStat);
}
}

The schedule method returns a ScheduledFuture which has a cancel method. You are currently discarding that.
Attempts to cancel execution of this task. This attempt will fail if
the task has already completed, has already been cancelled, or could
not be cancelled for some other reason. If successful, and this task
has not started when cancel is called, this task should never run. If
the task has already started, then the mayInterruptIfRunning parameter
determines whether the thread executing this task should be
interrupted in an attempt to stop the task.
After this method returns,
subsequent calls to isDone() will always return true. Subsequent calls
to isCancelled() will always return true if this method returned true.
In your case, if the task hasn't started then it will be removed from the queue of tasks, and it will be as if you never scheduled it.
If the task is currently running then you will not be able to cancel it, since you do not check the thread's interrupt flag. Whether you pass true or false to cancel() will not make a difference.
All your task does is push a task onto another thread, so the scheduled executor task is likely to be extremely quick. Because of that, it doesn't really matter that you don't check the interrupt flag. For longer running tasks which involve more steps - do X, then Y, then Z - then it would be important to check the flag.

Related

How can I make a thread in JavaFX?

I want to parse url after I input something in text dialog and comfirm.
But it will cost time, so I created a thread to do it:
public void onAddMod(ActionEvent actionEvent) throws IOException {
Platform.runLater(() -> {
status.setText("Downloading...");
TextInputDialog dialog = new TextInputDialog("fabric-api");
dialog.setTitle("Add a mod from Modrinth");
dialog.setHeaderText("");
dialog.setContentText("Modrinth slug: ");
Optional<String> opt = dialog.showAndWait();
if (opt.isPresent()) {
try {
// parsing
Information info = ModrinthUtils.information(opt.get());
DataSaver.MODS.put(info.modName(), info);
ModList.getItems().add(info.modName());
} catch (IOException ignored) {}
}
status.setText("Finished!");
});
}
*onAddMod() will run after the button pressed
However, Platform.runLater() CANNOT let the thread "run later". When I confirm, the program stopped until parsing finished.
How can I fix it? Where am I doing wrong?
Here's an excerpt from the documentation of Platform#runLater(Runnable):
Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted.
But your onAddMod method is already being invoked by the JavaFX Application Thread. So, all your current code is doing is delaying the code's execution by a small amount.
You need to modify your code to use a background thread. Creating a new thread is done the same way as in any other Java application. If you want just a one-off thread, then create a new Thread instance, passing it a Runnable, and call Thread#start(). If you want a thread pool, then make use of the Executor / ExecutorService API (also see the Executors class).
But as this is a JavaFX application, you probably also want to make use of the classes in the javafx.concurrent package. See Concurrency in JavaFX. For example:
public void onAddMod(ActionEvent actionEvent) {
// run dialog code on FX thread
TextInputDialog dialog = new TextInputDialog("fabric-api");
dialog.setTitle("Add a mod from Modrinth");
dialog.setHeaderText("");
dialog.setContentText("Modrinth slug: ");
Optional<String> opt = dialog.showAndWait();
if (opt.isPresent()) {
Task<Information> task = new Task<>() {
#Override
protected Information call() throws Exception {
// this method is invoked on the background thread
updateMessage("Downloading..."); // update coalesced on FX thread
Information info = ModrinthUtils.information(opt.get());
updateMessage("Finished!"); // update coalesced on FX thread
return info;
}
};
// might want to unbind when the task finishes?
status.textProperty().bind(task.messageProperty());
task.setOnSucceeded(event -> {
// this event handler is invoked on the FX thread
Information info = task.getValue();
DataSaver.MODS.put(info.modName(), info);
ModList.getItems().add(info.modName());
});
// this event handler is also invoked on the FX thread
task.setOnFailed(event -> /* notify user of failure */ );
Thread thread = new Thread(task); // 'Task' is a 'Runnable'
thread.setDaemon(true);
thread.start(); // start the background thread
}
}
The above makes use of Task and creates a one-off Thread instance. See Service for a reusable JavaFX worker, which also makes use of Executor so that you reuse the same thread pool.
I didn't know if:
DataSaver.MODS.put(info.modName(), info);
ModList.getItems().add(info.modName());
Needed to be executed on a certain thread, so I just put it on the FX thread, same as your current code, via the onSucceeded handler (the example puts the parsing on the background thread, if I understand your code correctly).

ExecutorService: get a list of submitted tasks and cancel all tasks without shutting down the executor

I have an ExecutorService that I submit tasks to:
private final ExecutorService CUSTOM_POOL = Executors
.newCachedThreadPool();
private Queue<Future<?>> customTasksHandles;
private boolean started;
private Runnable task = new Runnable() {
#Override
public void run() {
if (!started) {return;}
...
customTasksHandles.add(CUSTOM_POOL.submit(new CustomTask(customData)));
...
}
}
I need to create public void stop() and public void start() functions. The stop() function would future.cancel() for every task that has been submitted to the executor, while the start() would start running the task Runnable again.
public void stop() {
...
Future customTasksHandle= customTasksHandles.poll();
while (customTasksHandle!=null) {
customTasksHandle.cancel(true);
customTasksHandle=locationTasksHandles.poll();
}
...
started = false;
}
public void start() {started = true;}
I tried to just CUSTOM_POOL.shutdown(), however it seems to make it impossible to submit new tasks later, after the start() is called. It seems that the only way is to loop over all submitted tasks and call .cancel() on each.
However, how do I get all submitted tasks without adding each task to a list/queue when submitting? Is there a better way to do it other than the way above? I was hoping for a List<Future> submittedTasks = CUSTOM_POOL.getAllSubmittedTasks() method of sorts, but there doesn't seem to be one. Interestingly, .shutdown() and invoke() methods do return List<Future>, but have side-effects.
As you can see here you could use the shutdownNow() method to stop and retrieve all the task that where waiting for execution. If what you want is just stop ("pause") the procesing of the task and the continue with it, you migth want to keep track yourself of the status of the taks and when you pause and unapuse the task you can resubmit the task returned by the mehtod shutdownNow() and the one that where executing in the instant of the stop. You should take into account that to stop the threads the pool may call thread interrupt so, if you are executing some sensible work you should take care of it properly. There is no pause and unpause for threads. check this
You can achieve this by using Future, create start method which accepts Runnable and return Future
public Future<?> start(Runnable run) {
return CUSTOM_POOL.submit(run);
}
You can save all these Future in a List or Map so that you can cancel which ever you need by using custom stop method
public void stop(Future<?> future) {
future.cancel(true);
}
Example
public class TestMain {
private final ExecutorService CUSTOM_POOL = Executors
.newCachedThreadPool();
public static void main(String[] args) {
//custom logic
}
public Future<?> start(Runnable run) {
return CUSTOM_POOL.submit(run);
}
public void stop(Future<?> future) {
future.cancel(true);
}
}
Future
Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

javafx textfield doesn't change

I have the following code:
#FXML
private void test(){
textField.setText("Pending...");
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
And what I tries to achieve is that while the doStuff() do his stuff in a textField in the GUI there should be written "Pending..." and as soon as it finish it should change to "OK" / "Error".
I want that the GUI is blocked while doStuff is running so the user has to wait and can't click something else.
But what happens is that as soon as I start test it does the doStuff() but only updates the textField with "OK"/"Error" but I never see "Pending...".
I have the feeling that I have somehow update the GUI, but I'm not sure how it should be done.
Update:
What I tried is to move the doStuff in another Thread:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
public void run(){
boolean passed = doStuff();
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
}
};
t.start();
t.join();
}
It would works if i would remove the t.join(); command, but then the UI wouldn't be blocked. So I'm at a loss right now.
Thanks
You must never run long running tasks on the JavaFX Application Thread. Doing so will prevent said thread from doing any GUI related things which results in a frozen UI. This makes your user(s) sad. However, your attempt at putting the long running task on a background task is flawed. You call Thread.join which will block the calling thread until the target thread dies; this is effectively the same thing as just running the task on the calling thread.
For a quick fix to your example, you could do the following:
#FXML
private void test(){
textField.setText("Pending...");
Thread t = new Thread(){
#Override public void run(){
boolean passed = doStuff();
Platform.runLater(() -> {
if(passed){
textField.setText("OK");
} else {
textField.setText("Error");
}
});
}
};
t.start();
}
That will create a thread, start it, and let it run in the background while letting the JavaFX Application Thread continue doing what it needs to. Inside the background thread you must update the TextField inside a Platform.runLater(Runnable) call. This is needed because you must never update a live scene graph from a thread other than the JavaFX Application Thread; doing so will lead to undefined behavior. Also, you should look into “implements Runnable” vs “extends Thread” in Java. It's better, or at least more idiomatic, to do:
Thread t = new Thread(() -> { /* background code */ });
You could also use a javafx.concurrent.Task which may make it easier to communicate back to the JavaFX Application Thread. One option would be:
#FXML
private void test(){
textField.setText("Pending...");
Task<Boolean> task = new Task<>() {
#Override protected Boolean call() throws Exception {
return doStuff();
}
};
task.setOnSucceeded(event -> textField.setText(task.getValue() ? "Ok" : "Error"));
new Thread(task).start();
}
You could also bind the TextField to the message property of the Task and call updateMessage("Pending...") inside the call method. You could even provide more detailed messages if and when possible.
That said, creating and starting Threads yourself is not ideal and you should look into thread pooling (using something like an ExecutorService). You might also want to look into javafx.concurrent.Service for "reusing" Tasks.
For more information about JavaFX concurrency see Concurrency in JavaFX and read the documentation of the classes in javafx.concurrent. For the basics of multi-threading in Java see Lesson: Concurrency from The Java™ Tutorials.

Handling regular jobs with possible manual start in Java

I'm trying to solve a problem similar to downloading new mails from mail servers by mail client. I have a task, which is performed regularly (next iteration is 10 minutes after the last one ends for example) but there is also a possibility to run the task manually.
When I am trying to run the job manually and job is not running at this moment (is appointed for later), I cancel the appointment and schedule it for now. When the job is already running I do not cancel it, but wait until it finishes and run it again. But only one task can wait this way.
My problem is that I do not know how to synchronize the jobs to make it thread safe and make sure that job never runs twice at the same time.
To make it more clear. The main problem is that simple asking if the job is running and deciding based on what I get is not enough, because between the question and action the situation can change. It is a short span but the probability is not zero. And the same problem is with deciding if I should run the job again at the end of his run. If my decision is based on the value of some variable or some other if clause, then between testing its value and performing some action, some other thread can change it and I can end up with two scheduled jobs or none at all.
Have you considered using a DelayQueue?
To make your job run now you just need to persuade it to return 0 form getDelay(TimeUnit unit).
The main way to do that check you are telling about is to check, to lock and after that to repeat the same check:
public class OneQueuedThread extends Thread {
static int numberRunning =0;
public void run() {
if (numberRunning<2) {
synchronized (OneQueuedThread.class){
if (numberRunning<2) {
numberRunning++;
// ---------------your process runs here
numberRunning--;
}
}
}
}
}
So, only one attempt to run the thread while it is already running will wait until the end of the running thread and start after it.
As for scheduling, let's use the TimerTask features:
public class ScheduledTask extends TimerTask {
ScheduledTask instance;
/**
* this constructor is to be used for manual launching
*/
public void ScheduledTask(){
if (instance == null){
instance = this;
} else {
instance.cancel();
}
instance.run();
}
/**
* This constructor is to be used for scheduled launching
* #param deltaTime
*/
public ScheduledTask(long deltaTime){
instance = this;
Timer timer = new Timer();
timer.schedule(instance, deltaTime);
}
public void run() {
OneQueuedThread currentTread;
currentTread = new OneQueuedThread();
currentTread.start();
}
}

AsyncTask on button click = task canceled or still running?

If I have a button action that launches an AsyncTask. What if I click the button 10 times? Is the task executes 10 times and runs that much in background? Or is any previous task canceled and just the task reinitiated (that would be my desired behaviour)?
public void myButtonClick(View v) {
new MyAsyncTask().execute(params);
}
Each click on the button is producing a new instance of your MyAsyncTask, that means that each task is created and executed until its ends.
If you want to stop a previously launched task, you have to store a reference to the last launched task and cancel it like:
public void myButtonClick(View v) {
if (this.lastMyAsyncTask != null && this.lastMyAsyncTask.getStatus() != AsyncTask.Status.FINISHED){
this.lastMyAsyncTask.cancel();
}
this.lastMyAsyncTask = new MyAsyncTask();
this.lastMyAsyncTask.execute(params);
}
If you really want to keep the task to a single execution at a time, you should create a single threaded ThreadPool:
private ThreadPoolExecutor executor =
new ThreadPoolExecutor(1,1, 1, TimeUnit.SECONDS,
new SynchronousQueue(), new ThreadPoolExecutor.AbortPolicy());
private Future<Void> lastSubmitted;
private ReentrantLock submissionLock = new ReentrantLock();
...
try {
submissionLock.lock();
Future<Void> future =
executor.submit(new MyAsyncTask()); // assuming this implements Callable<Void>
lastSubmitted = future;
}
catch (RejectedExecutionException e) {
lastSubmitted.cancel();
Future<Void> future =
executor.submit(new MyAsyncTask()); // assuming this implements Callable<Void>
lastSubmitted = future;
}
finally {
submissionLock.unlock();
}
If you do it as suggested above, you'll get a RejectedExecutionException if you try to submit a task while one is already running. When that happens, you can cancel the last one submitted, and schedule a new execution. Keep in mind your task has to be interruptible (be careful with InputStream/OutputStream as they are not interruptible, use Channel objects instead).
The lock is needed so that you serialize the submission attempts. A thread could still get a RejectedExecutionException if another thread submitted a task during the catch block execution.

Categories