I'm using a Task to read a text file, the task is invoked when the user clicks "open file" menu, it is supposed to read the text file, and then update the local variable "text", the problem occurs at the first try, if i open a file, nothing happens, and the value of the text string stays as it is, if i open any file again, everything works as expected, i couldn't find the cause of this.
The method that has the task
private void readFile(File file){
Task<String> task = new Task<String>() {
#Override
protected String call() {
List<String> list = EditorUtils.readFromFile(file);
String str = list.stream().collect(Collectors.joining("\n"));
return str;
}
};
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t) {
setCurrentText(task.getValue());
}
});
task.setOnFailed(e -> setCurrentText("FAILED"));
Thread t = new Thread(task);
t.setDaemon(true);
t.start();
}
SetCurrentText
private void setCurrentText(String text){
this.text = text;
}
The method of the controller
#FXML
void openMenuItemClick(ActionEvent event) {
fileChooser.setTitle("title");
fileChooser.getExtensionFilters().add
(new FileChooser.ExtensionFilter("TXT files (*.txt)", "*.txt"));
File file = fileChooser.showOpenDialog(open.getParentPopup().getScene().getWindow());
if (file != null){
readFile(file);
System.out.println(text); //prints null since "text" isn't initialized yet
}
}
EditorUtils#readFromFile
public static List<String> readFromFile(File file){
List<String> lines = new ArrayList<>();
try {
lines = Files.readAllLines(Paths.get(file.getPath()), StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
return lines;
}
Your readFile method creates a Task, gives it to a Thread, launches the Thread, and returns. You then try to immediately print out the value of text. There's no guarantee your Task will have completed by the time you call println(text). In fact, it's very likely your Task has not completed yet. But that's not the only problem.
The call to readFile and println are both done on the same thread—in this case, the JavaFX Application Thread. The problem here is that the EventHandler you pass to setOnSucceeded will be invoked on the FX thread as well. The way this is achieved internally is with a Platform.runLater call which schedules the action with the FX thread to be ran some time in the future. This can't happen while the FX thread is executing openMenuItemClick and must wait until the method returns.
What this all means is that setCurrentText will never run until after the call to println. But by the second time openMenuItemClick is invoked the text will have been set1. So what you're seeing the second time is actually the result of the first Task.
If you want to do something with text once the Task completes then you should do it inside the onSucceeded or onFailed handler. Or you can make text a StringProperty and observe it for changes.
1. Technically, it may have been set. There's still no guarantee the Task has completed by then.
This is perfectly normal behaviour when using multiple threads. You access the file from a task running on background thread. On completion this task triggers an update on the JavaFX application thread.
By the time readFile returns the task may not have been completed. The fact that Task uses Platform.runLater to execute the onSucceeded handler results in this handler never being invoked before the openMenuItemClick method completes, even if the file is read before System.out.println is reached.
If you need to update the GUI based on the result of the Task, you should do so from the event handler. The code updating the text field runs after the System.out.println(text); statement. The second time you start the task, you print results of the task started the first time the menu item was clicked, not the new one. You can verify this by moving the println to the beginning of the openMenuItemClick method.
Related
In my JavaFX-11 fxml application, I have a simple test Task:
public class TestTask extends Task<Void> {
#Override
protected Void call() throws Exception {
updateMessage("TestTask message before sleep 1");
Thread.sleep(1000);
updateMessage("TestTask message after sleep 1");
Thread.sleep(1000);
updateMessage("TestTask message after sleep 2");
Thread.sleep(1000);
updateMessage("TestTask message after sleep 3");
return null;
}
}
In the fxml controller class, I have bound a property to the Task message property, and a change listener that I want to use to write the messages in a log window:
TestTask testTask = new TestTask();
SimpleStringProperty taskMessage = new SimpleStringProperty();
taskMessage.bind(testTask.messageProperty());
taskMessage.addListener(cl -> {
System.out.println("taskMessage change listener invoked, new value = " + taskMessage.getValue());
msgList.add(taskMessage.getValue());
});
I start the thread like this:
Thread computationThread = new Thread(testTask);
computationThread.setDaemon(true);
computationThread.start();
The console shows the following messages:
taskMessage change listener invoked, new value =
taskMessage change listener invoked, new value = TestTask message after sleep 3
I know that "intermediate message values may be coalesced to save on event notifications", but here we are talking about only 4 messages, separated by 1 second. They can hardly swamp the event queue, so I don't understand why the bound property is only updated twice: once with an empty message and a second time with the last message.
Using a ChangeListener is the correct way of doing it.
When I started writing the lambda expression, the IDE suggested a list of applicable listeners and I had accidentally accepted the suggestion of an InvalidationListener.
I'm trying to get a responsive JavaFX graphical interface while executing a cmd command.
The command I'm executing is the following.
youtube-dl.exe --audio-format mp3 --extract-audio https://www.youtube.com/watch?v=l2vy6pJSo9c
As you see this is a youtube-downloader that converts a youtube link to an mp3-file.
I want this to be executed in a second thread and not in the main FX thread.
I've solved this by implementing interface Callable in the class StartDownloadingThread.
#Override
public Process call() throws Exception {
Process p = null;
p = ExecuteCommand(localCPara1, localCPara2, localDirectory).start();
try {
Thread.sleep(30);
}catch (InterruptedException e){}
return p;
}
The method ExecuteCommand just returns a ProcessBuilder object.
I try to use Thread.sleep to make the program return to the main thread and thus making the application responsive. Unfortunately the program still freezes.
This is how the method call is called.
ExecutorService pool = Executors.newFixedThreadPool(2);
StartDownloadingThread callable = new StartDownloadingThread(parameter1, parameter2, directory);
Future future = pool.submit(callable);
Process p = (Process) future.get();
p.waitFor();
How do I make my GUI responsive using the interface Callable?
Using a executor to run a task just for you to use the get method of the Future that is returned when submitting the task does not actually free the original thread to continue with other tasks. Later you even use the waitFor method on the original thread, which is likely to take even more time than anything you do in your Callable.
For this purpose the Task class may be better suited, since it allows you to handle success/failure on the application thread using event handlers.
Also please make sure an ExecutorService is shut down after you're done submitting tasks.
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
Process p = null;
p = ExecuteCommand(localCPara1, localCPara2, localDirectory).start();
// why are you even doing this?
try {
Thread.sleep(30);
}catch (InterruptedException e){}
// do the rest of the long running things
p.waitFor();
return null;
}
};
task.setOnSucceeded(event -> {
// modify ui to show success
});
task.setOnFailed(event -> {
// modify ui to show failure
});
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(task);
// add more tasks...
// shutdown the pool not keep the jvm alive because of the pool
pool.shutdown();
I got some operations in my Controller class which could take some time. So I want to show a loading dialog while this operation is running.
I tried this:
Platform.runLater(new Runnable() {
#Override
public void run() {
loadingDialog.show();
}
});
Boolean opSuccess = myService.operate();
Platform.runLater(new Runnable() {
#Override
public void run() {
loadingDialog.hide();
}
});
if (opSuccess) {
// continue
}
Now, the Problem is, the loadingDialog is never show. The UI only blocks for some time and than continues on "//continue".
So it seems, the runLater call is blocked by the blocking operation (operate)?
I also tried CoundDownLatch, to wait for loadingDialog.show() to complete, before running myService.operate(). But the latch.await() method never completes.
So my question is, how my I show the loadingDialog until myService.operate() finished and returned true or false? Do I have to put the operate() call into another thread and run it async or is there an easier way?
Thanks for help.
Are you sure your entire code does not run in the JavaFX Thread?
Methods of your controller class usually do and I assume it due to your description.
However, better use the Task class. Here you'll find a tutorial and a short snippet for your application:
// here runs the JavaFX thread
// Boolean as generic parameter since you want to return it
Task<Boolean> task = new Task<Boolean>() {
#Override public Boolean call() {
// do your operation in here
return myService.operate();
}
};
task.setOnRunning((e) -> loadingDialog.show());
task.setOnSucceeded((e) -> {
loadingDialog.hide();
Boolean returnValue = task.get();
// process return value again in JavaFX thread
});
task.setOnFailed((e) -> {
// eventual error handling by catching exceptions from task.get()
});
new Thread(task).start();
I assumed Java 8 and the possibility to use Lambda expressions. Of course it is possible without them.
You are better off making use of concurrency mechanisms/Worker interfaces in JavaFx - Tasks and services instead of using Platform.runLater(). The tasks and services allow you to manage the long running tasks in a separate thread. They also provide callbacks to indicate the progress of the tasks.
You could explore further at http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm
Also have a look at the Ensemble JavaFX samples for Tasks and Services - http://www.oracle.com/technetwork/java/javase/overview/javafx-samples-2158687.html
I'm just getting my feet wet with the SysOperation framework and I have some ReliableAsynchronous processes that run and call info("starting...") etc.
I want these infolog messages so that when I look in the BatchHistory, I can see them for purposes of investigating later.
But they also launch to the client, from the batch. And I can tell they're from the batch because you can't double click on the infologs to go to the source. Is there someway to either suppress these from popping up on the user's screen and only show in the batch log?
EDIT with some code:
User clicks a button on form action pane that calls an action menu item referencing a class.
In the class, the new method:
public void new()
{
super();
this.parmClassName(classStr(MyControllerClass));
this.parmMethodName(methodStr(MyControllerClass, pickTransLines));
this.parmExecutionMode(SysOperationExecutionMode::ReliableAsynchronous);
// This is meant to be running as a batch task, so don't load syslastvalue
this.parmLoadFromSysLastValue(false);
}
The main method hit from the menu item:
public static void main (Args _args)
{
MyControllerClass controller = new MyControllerClass();
MyContract contract;
WMSOrderTrans wmsOrderTrans;
RefRecId refRecId;
if (_args && _args.dataset() == tableNum(WMSOrderTrans) && _args.record())
{
contract = controller.getDataContractObject();
contract.parmRefRecId(_args.record().RecId);
controller.parmShowDialog(false);
refRecId = controller.doBatch().BatchJobId;
// This creates a batch tracking record
controller.updateCreateTracking(refRecId, _args.record().RecId);
}
}
The controller method that gets launched:
// Main picking method
private void pickTransLines(MyContract_contract)
{
MyTrackingTable tracking;
boolean finished;
BatchHeader batchHeader = BatchHeader::getCurrentBatchHeader();
boolean updateTracking = false;
// NOTE - This infolog launches after a few seconds to the user, but
// you can't double click on the info message to go to the code
// because it's fired from the batch somehow.
info(strFmt("Working on wmsordertrans.recid == %1", _contract.parmRefRecId()));
// Create/Update batch tracker if needed
if (this.isInBatch())
{
// NOTE - This code gets executed so we ARE in batch
this.updateTrackingStuff(...);
}
// Do the pick work
finished = this.doPick(_contract);
if(!finished)
throw error("An error occurred during the picking process.");
}
Then a split second later this launches to my session:
Look at the SysOperationServiceController.afterOperation method,:
[...]
if (_executionMode == SysOperationExecutionMode::ReliableAsynchronous)
{
batch = this.operationReturnValue();
if (batch)
{
infolog.view(Batch::showLog(batch.RecId));
}
}
[...]
This is the code that shows the infolog to the screen for reliable asynchronous processed.
You can create your own controller by extending SysOperationServiceController and use that on your menu item or in code, so do that and overwrite the afterOperation on your new controller, for example like this (didn't test but should work in your case):
if (_executionMode != SysOperationExecutionMode::ReliableAsynchronous)
{
super(_executionMode, _asyncResult);
}
I am learning WF4 and got stuck at the following place. Please help.Thanks.
1) I have created a static method, MyMethod in a static class called Worker. Within this method I call Thread.Sleep(3000) and then print "MyMethod" called.
2) I then created an activity, DoWork (DoWork.xaml) which consists of a InvokeMethod (The target type is the Worker class in step 1 and MethodName = MyMethod).
3) In the main method, I call 2 methods called OutputSequence() and OutputParallel() which are as follows
private static void OutputSequence()
{
Sequence s = new Sequence() { Activities = new DoWork(), new DoWork() } };
WorkflowInvoker.Invoke(s);
}
private static void OutputParallel()
{
Parallel p = new Parallel() { Branches = new DoWork(), new DoWork() } };
WorkflowInvoker.Invoke(p);
}
The OutputSequence() is OK as it calls the target method twice (in sequence) but the parallel one seems to execute sequentially as well. I expected it to execute in parallel.
What am I missing.
The Parallel activity is not what you think it is - it allows you to wait for things in parallel not to execute CPU based code in parallel. The WF4 threading mode is that there is exactly one thread at a time active in the workflow.
If you put two delays in the parallel then both of those waits would occur in parallel as opposed to sequentially as they would in a sequence
The idea is you want to wait for a number of actions when you don;t know the order in which they will occur. Then the parallel activity is complete when all of its child branches have completed
Actually Parallel activity really executes all branches one-by-one and has nothing related to concurrent code execution, like two thread do.
But there is MS sample, that shows "true" concurrent execution for blocks inside of parallel activity. There is the AsyncCodeActivity in the .net 4 that allows to get concurrent execution of activities. Please check http://msdn.microsoft.com/en-us/library/ee358731(VS.100).aspx
Below you can find copy-pasted sample from link above:
public sealed class GenerateRandom : AsyncCodeActivity<int>
{
static Random r = new Random();
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
// Create a delegate that references the method that implements
// the asynchronous work. Assign the delegate to the UserState,
// invoke the delegate, and return the resulting IAsyncResult.
Func<int> GetRandomDelegate = new Func<int>(GetRandom);
context.UserState = GetRandomDelegate;
return GetRandomDelegate.BeginInvoke(callback, state);
}
protected override int EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
// Get the delegate from the UserState and call EndInvoke
Func<int> GetRandomDelegate = (Func<int>)context.UserState;
return (int)GetRandomDelegate.EndInvoke(result);
}
int GetRandom()
{
// This activity simulates taking a few moments
// to generate the random number. This code runs
// asynchronously with respect to the workflow thread.
Thread.Sleep(5000);
return r.Next(1, 101);
}
}
hope this will help for someone else