Create a background task in IntelliJ plugin - java

I'm developing an IntelliJ-idea plugin and want to run code in background task (visible in the background tasks dialog and in another thread than the UI).
I found the following Helper class and tried it by passing a Runnable object and implement its run method but it still blocking the UI and when I tried to do the threading myself i got the following error
Read access is allowed from event dispatch thread or inside read-action only (see com.intellij.openapi.application.Application.runReadAction())
Details: Current thread: Thread[Thread-69 [WriteAccessToken],6,Idea Thread Group] 532224832
Our dispatch thread:Thread[AWT-EventQueue-1 12.1.4#IU-129.713, eap:false,6,Idea Thread Group] 324031064
SystemEventQueueThread: Thread[AWT-EventQueue-1 12.1.4#IU-129.713, eap:false,6,Idea Thread Group] 324031064

I have found a better way to run the process as background task where you can update the progress bar percentage and text
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Title"){
public void run(#NotNull ProgressIndicator progressIndicator) {
// start your process
// Set the progress bar percentage and text
progressIndicator.setFraction(0.10);
progressIndicator.setText("90% to finish");
// 50% done
progressIndicator.setFraction(0.50);
progressIndicator.setText("50% to finish");
// Finished
progressIndicator.setFraction(1.0);
progressIndicator.setText("finished");
}});
If you need to read some data from another thread you should use
AccessToken token = null;
try {
token = ApplicationManager.getApplication().acquireReadActionLock();
//do what you need
} finally {
token.finish();
}

Here is the general solution
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
// do whatever you need to do
}
});
}
});

New way to run backgroundable task with kotlin
import com.intellij.openapi.progress.runBackgroundableTask
runBackgroundableTask("My Backgrund Task", project) {
for (i in 0..10 step 1) {
it.checkCanceled()
it.fraction = i / 10.0
sleep(i * 100L)
}
}

As described in API :
Causes doRun.run() to be executed asynchronously on the AWT event
dispatching thread. This will happen after all pending AWT events have
been processed. This method should be used when an application thread
needs to update the GUI.
SwingUtilities.invokeLater {
// do something
}

Related

How to run multiple thread properly in eclipse plugin?

My plugin checks if the user is idle for a certain amount of time. With the solution from here, I tried to work my way around. But the eclipse application becomes unresponsive until the loop ends.
Also, the message box is just a plain box with no title and buttons. Can somebody tell me what is wrong with this code?
#Override
public void earlyStartup() {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
//while(true) {
for (stop=System.nanoTime()+TimeUnit.MINUTES.toNanos(1);stop>System.nanoTime();) {
Display.getDefault().addFilter(SWT.KeyUp, new Listener() {
#Override
public void handleEvent(Event event) {
stop=System.nanoTime()+TimeUnit.MINUTES.toNanos(1);
System.out.println("checkpoint 1");
}
});
}
Shell shell = new Shell(Display.getDefault());
MessageBox dialog =
new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
dialog.setText("Alert!");
dialog.setMessage("You have been idle for the last 3 minutes.");
shell.open();
stop=System.nanoTime()+TimeUnit.MINUTES.toNanos(1);
System.out.println("checkpoint 2");
}
});
}
Display.asyncExec does not run code in a separate thread. It runs the code in the main UI thread as soon as it is available. The UI thread will be blocked until the code ends.
Instead you can run code in a normal Java thread. But you must call asyncExec to execute any UI code you want to run from the thread.
In your actual code you should only be calling Display.addFilter once. This adds a listener which will be called every time the key up event occurs from then onwards. Since this is UI code you can't actually run this in a background thread at all.
So you can't use a loop like you have shown. You have to keep track of things in the key listener, updating each time the listener is called.

How to use SimpleTimeLimiter (Guava) with Vaadin

I'm trying to call some code within the vaadin framework which is longer running that will update the screen using push, however if the process is taking too long I want to be able to cancel it.
With that in mind I'm trying to use Guava's SimpleTimeLimiter class but no matter what I do I can't seem to stop the Vaadin process from stopping. I've tried both to put the SimpleTimeLimiter inside UI.getCurrent().access() method and outside of it but they both just continue to execute the process even if SimpleTimeLimiter throws a TimeoutException. However if I use the same code with a normal thread it seems to work...
public static void limitExecutionTime(Consumer<UI> lambda)
{
UI currentUI = UI.getCurrent();
UI.getCurrent().access(() ->
{
try
{
SimpleTimeLimiter.create(Executors.newSingleThreadExecutor()).callWithTimeout(new Callable<Void>()
{
#Override
public Void call()
{
// This is needed to deal how Vaadin 8 handles UI's
UI.setCurrent(currentUI);
lambda.accept();
return null;
}
}, 1, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException | ExecutionException e) {
NotificationUtils.showError("Execution took beyond the maximum allowed time.");
currentUI.push();
}
});
}
In the above code if the method takes more than 1 second it will throw a TimeoutException and put up the notification window. However it will continue to execute the lambda.
As a result I've tried to do the opposite and put UI.getCurrent().access() in the public Void call() method but this had the exact same result...
You should call UI.access after your background task is ready to update it with some data. You use access method to do changes on the page that the user is viewing.
Background task execution
In your example, you are missing a way to pass task cancellation message to call method. In order to prepare for task cancellation from external event (for example cancel button click) then you need to take this into account in inside the task. The following example shows how you can offer cancel method using Future.cancel.
private void onCancelClick(Button.ClickEvent clickEvent) {
// This method is called from Vaadin UI thread. We will signal
// background task thread to stop.
futureResult.cancel(true);
}
Inside the actual task this can be handled in the following ways
private void simulateLongAndSlowCalculation() {
while (moreWorkTodo) {
if (Thread.currentThread().isInterrupted()) {
return;
}
try {
doSomeBlockingCallThatCanBeInterrupted();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
Starting task and UI.access
When starting task then view should create task and submit it to executor service.
private void onButtonClick(Button.ClickEvent clickEvent) {
// This runTask creates important link to current UI and the background task.
// "this" object in these onTask methods is the UI object that we want
// to update. We need to have someway to pass UI object to background
// thread. UI.getCurrent() could be a parameter that is passed to the
// task as well.
Future<String> futureResult = taskService.runTask(
this::onTaskDone,
this::onTaskCancel,
this::onTaskProgress);
progressDialog = new ProgressDialog(futureResult);
progressDialog.show();
}
Now UI.access method is only needed when we want to update UI. In this example, that can happen in the following cases
Task completed successfully
Task progress was updated
Task got cancelled
Note that all of the following methods this refers to the UI object that started the task. So we are updating the correct UI with result and not some other user's UI.
You should not need to call UI.setCurrent in your code.
private void onTaskProgress(double progress) {
logger.info("onTaskProgress: {}", progress);
access(() -> progressDialog.setProgress(progress));
}
private void onTaskCancel() {
logger.info("onTaskCancel");
access(() -> {
progressDialog.close();
setResult("Cancelled");
});
}
private void onTaskDone(String result) {
logger.info("onTaskDone");
access(() -> {
progressDialog.close();
setResult(result);
});
}
Example project
I pushed another project to github that shows how to cancel a background task from cancel button:
https://github.com/m1kah/vaadin-background-task
Edit: Added sections about background tasks and UI.access. Updated example project link to another example.

JavaFX 2 StringProperty does not update field until enclosing method returns

I would like to update a Label in a JavaFX application so that the text changes multiple times as the method runs:
private void analyze(){
labelString.setValue("Analyzing"); // (labelString is bound to the Label during initialization)
// <Some time consuming task here>
labelString.setValue("Analysis complete!");
}
But when I run this, the label does not update until the task finishes, and just displays whatever it was before until the analyze() method returns.
How can I force update the label so that it will show "Analyzing" in the beginning followed by "Analysis complete!" when the task is complete?
Assuming you are invoking your analyze() method on the FX Application Thread (e.g. in an event handler), your time consuming code is blocking that thread and preventing the UI from updating until it is complete. As #glen3b says in the comments, you need to use an external thread to manage this code.
JavaFX provides a Task API which helps you do this. In particular, it provides methods which invoke code on the Java FX Application thread for you, allowing you to update the UI safely from your background Task.
So you can do something like
private void analyze() {
Task<Void> task = new Task<Void>() {
public Void call() {
updateMessage("Analyzing");
// time consuming task here
updateMessage("Analysis complete");
}
};
labelString.bind(task.messageProperty());
new Thread(task).start();
}
If you need to unbind the StringProperty when the task is complete, you can do
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
labelString.unbind();
}
});

Why is this thread running multiple times in a row?

I just solved the problem myself. I had multiple calls for syncCustomers() due to a dialog closing event problem. I solved it by providing the parent JFrame in the JDialog constructor. Pretty stupid error on my side.
My application contains a task that synchronizes with a webservice and a local database. This task may take up to several minutes. Thus I want to notify the user about this time consuming process with a simple dialog (Swing). The user is not supposed to continue working while the sync process is running.
So I thought of:
open modal dialog with the notification for the user
start the sync process in a separate thread
close modal dialog after sync process is done
User clicked on the button to start sync process:
private void syncCustomers() {
if (checkWebserviceAuth()) {
SyncDialog dialog = new SyncDialog();
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
SyncCustomersTask task = new SyncCustomersTask(dialog, getCoach());
task.run(); // task.start() will result in the same problem
} else {
openAuthorizeDialog(true);
}
}
public class SyncDialog extends javax.swing.JDialog {
public SyncDialog() {
initComponents();
// I already noticed that the modal dialog won't work for me since it interrupts within syncCustomers()
//this.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
this.setTitle(Application.getApplicationTitle());
}
...
}
public class SyncCustomersTask extends Thread {
private void doWork() {
System.out.println("Start doWork() and sleep for 10 seconds...");
try {
// for testing purpose
Thread.sleep(10000);
} catch (InterruptedException ex) {
}
System.out.println("Done with doWork().");
}
#Override
public void run() {
doWork();
if (getCallback() != null) {
System.out.println("Invoke callback...");
getCallback().dispose();
System.out.println("Callback invoked.");
}
}
...
}
This will result in an infinite loop of:
Start with doWork()...
Start doWork() and sleep for 10 seconds...
Done with doWork().
Invoke callback...
Callback invoked.
If I comment out
getCallback().dispose();
, the loop will stop after the second execution:
Start with doWork()...
Start doWork() and sleep for 10 seconds...
Done with doWork().
Invoke callback...
Callback invoked.
Start with doWork()...
Start doWork() and sleep for 10 seconds...
Done with doWork().
Invoke callback...
Callback invoked.
I don't get it. What fires the thread to execute over and over again?
I guess this whole thing isn't a good idea to start with, but I wasn't able to get things like ProgressMonitor working either. :(
Call start(), not run(). The latter will simply execute the thread, but not in a separate thread! The start() method will instantiate a new thread, and only then invoke your run() method in that new thread.
This is a surprising common problem, btw.
invoking run() does not execute code in a new thread.

Output to jTextArea in realtime

I have some code which takes a few minutes to process, it has to connect to the web for each string in a long array, each string is a url. I want to make it so that everytime it connects, it should refresh the jtextarea so that the user is not staring into a blank page that looks frozen for 20 min. or however long it takes. here is an example of something i tried and didnt work:
try {
ArrayList<String> myLinks = LinkParser.getmyLinksArray(jTextArea1.getText());
for (String s : myLinks) {
jTextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
}
} catch (IOException ex) {
JOptionPane.showMessageDialog(jTextArea1, "Parsing Error", "Parsing Error", JOptionPane.ERROR_MESSAGE);
Logger.getLogger(MYView.class.getName()).log(Level.SEVERE, null, ex);
}
The problem is that you need to perform the computation asynchronously. You should create a background thread that performs the computation, and then use SwingUtilities.invokeLater to update the JTextArea.
final ArrayList<String> myLinks = //...
(new Thread()
{
public void run(){
for (String s : myLinks) {
try{
final String result = LinkChecker.checkFileStatus(s) + "\n";
SwingUtilities.invokeLater(new Runnable(){
public void run(){
jtextArea2.append(result);
}
});
}catch(IOException error){
// handle error
}
}
}
}).start();
Edit
It has been pointed out that JTextArea's append function actually is thread safe (unlike most Swing functions). Therefore, for this particular, case it is not necessary to update it via invokeLater. However, you should still do you processing in a background thread so as to allow the GUI to update, so the code is:
final ArrayList<String> myLinks = //...
(new Thread()
{
public void run(){
for (String s : myLinks) {
try{
jtextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
}catch(IOException error){
// handle error
}
}
}
}).start();
However, for pretty much any other operation that modifies a Swing object, you will need to use invokeLater (to ensure the modification occurs in the GUI thread), since almost all the Swing functions aren't thread safe.
You need to investigate threading and its relationship to GUI updates in Swing. Anything that affects or makes use of GUI components in Swing must done on a special thread called the Event Dispatch Thread (EDT).
If your code snippet, if it's freezing the GUI, I imagine that it is being run in the EDT. Performing a long-running action on the EDT will make the GUI unresponsive, because no further updates can be done while your long-running process is using the thread.
There is a helper class called SwingWorker that allows you to offload long-running computations to a background thread, and then make updates to the GUI thread when it is complete. The SwingWorker looks after the context switches between the GUI thread and the background thread. You can also display progress bars to let the user know the state of the long-running process, so they know your application hasn't hung.
swing/awt is a single threaded library, so once a component is shown, just changing it's appearance won't work correctly. You need to change the component on the GUI Thread, not from your thread. To do this wrap any code that updates a component with SwingUtilities.invokeLater... as in
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
jTextArea2.append(LinkChecker.checkFileStatus(s) + "\n");
}
});
also you want to limit what you do on the gui thread to avoid the gui from becoming sluggish, so if checkFileStatus is time consuming, execute it outside the run method and store the result in a final local variable, and just access the variable in the run() code.

Categories

Resources