I have some code which executes a download in a separate thread, created so that the JFrame GUI will continue to update during the download. But, the purpose is completely defeated when I use Thread.join(), as it causes the GUI to stop updating. I need a way to wait for the thread to finish and still update the GUI.
You can have the task that does the download also fire an event to the GUI.
For example:
Runnable task = new Runnable() {
public void run() {
// do your download
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// call some method to tell the GUI that the download finished.
}
});
}
};
and then to run it, either use an Executor (preferred method) or a raw thread:
executor.execute(task);
or
new Thread(task).start();
As pointed out in the comments, you'd generally use a SwingWorker to do this kind of thing but you can also do the manual approach outlined above.
SwingWorker provides a doInBackground method where you would stick your download logic in, a done method where you would stick in code to notify the GUI that the download finished and a get method to get the result of doInBackground (if there was one).
E.g.,
class Downloader extends SwingWorker<Object, Object> {
#Override
public Object doInBackground() {
return doDownload();
}
#Override
protected void done() {
try {
frame.downloadDone(get());
} catch (Exception ignore) {
}
}
}
(new Downloader()).execute();
Related
I am running out of ideas how to make my progress bar responsive during performing RMI connection, so I have decided to ask You for help.
Here's the code :
Thread performLogin = new Thread(new Runnable()
{
#Override
public void run()
{
LoginResult = TryLogin();
}
});
performLogin.start();
WaiterFrame.setVisible(true);
SetProgressDialog();
try
{
performLogin.join();
}
catch(InterruptedException exc)
{
System.err.println(exc.getLocalizedMessage());
}
if (LoginResult)
{ ... }
WaiterFrame.setVisible(false);
this.dispose();
Progress bar is unresponsive - does not animate as it should while performing performLogin thread. I was trying to run progress bar frame on the other thread too, but result was the same (as well as using Eventqueue.invokelater()).
The likely cause is performLogin.join(); is blocking the Event Dispatching Thread, making the UI non-responsive.
Two things to remember with Swing (and most GUI frameworks);
It is single threaded, meaning if your block the EDT for any reason, it will no longer able to process new events or perform repaints
It's not thread safe, so you should never modify the state of the UI from outside the context of the EDT.
You could use a SwingWorker, which would allow you to run your long running process in a background thread but provides a number of mechanism through which you can send updates back to the EDT safely.
See Worker Threads and SwingWorker for more details and Issues with SwingWorker and JProgressBar for an example
If you're using Java 8 you could try something like this:
CompletableFuture<LoginResult> loginResult = CompletableFuture.supplyAsync(this::tryLogin);
WaiterFrame.setVisible(true);
setProgressDialog();
loginResult.thenAccept(lr -> {
//do your thing
WaiterFrame.setVisible(false);
})
There are other options to "thenAccept" depending on what you need to do. "thenAccept" only consumes the the content of the Future.
The same could be accomplished using Guava's ListenableFuture and Executors if Java 8 is not an option.
Thank You very much MadProgrammer! Progress bar works as intended with SwingWorker usage. I'm posting code if someone would encourage same problem in future :
PerformLogin = new SwingWorker<Boolean, Object>()
{
#Override
protected Boolean doInBackground() throws Exception
{
LoginResult = TryLogin();
if (LoginResult)
{
MainF = new MainFrame();
MainF.Connection = DataEntry.TestConnection;
MainF.prepareFormToShow();
}
return LoginResult;
}
#Override
protected void done()
{
if (LoginResult == true)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
MainF.setVisible(true);
WaiterFrame.setVisible(false);
}
});
}
else
{
setVisible(true);
this.cancel(true);
JOptionPane.showMessageDialog(null, "Wrong adress!",
"Błąd",JOptionPane.WARNING_MESSAGE);
}
}
and
WaiterFrame.setVisible(true);
PerformLogin.execute();
in the main thread
I need to call the same thread multiple times in my app. Using my original code, the first time is can be executed just fine. But the second time it crashes - I then learned that each thread shall only be executed not more than one time.
My original piece of code:
View.OnClickListener myClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
mythread.start();
}
};
Thread mythread = new Thread(){
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
demoBt.setText("Running...");
}
});
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
public void run() {
demoBt.setText("Finished...");
}
});
}
};
So as I said, it crashes if I try to run it for the second time. So I tried modifying it like:
View.OnClickListener myClickListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
test();
}
};
private void test(){
Thread mythread = new Thread(){
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
demoBt.setText("Running...");
}
});
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
public void run() {
demoBt.setText("Finished...");
}
});
}
};
mythread.start();
}
It works very good; but my question is that whether this is the correct way to do this action or there is a more optimal way to do this?
Also, is it an acceptable thing to call a thread from insider of another thread? (like the way I put stuff on UI Thread inside the new thread of mine)
EDIT:
This is just an example. For my actual code I have heavy math-based simulation to be done which takes 10sec to be done. Based on the results that will be shown to the user , they may want to change their input parameters and let the simulation run again. This will happen several times.
In addition to the other good answers about using AsyncTask or runOnUiThread(), you could define a private member as a Runnable, like this:
private Runnable mytask = new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
demoBt.setText("Running...");
}
});
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
public void run() {
demoBt.setText("Finished...");
}
});
}
};
Then, whenever you want to run it, do
new Thread(mytask).start();
There is nothing bad with that but I think a better way would be using AsyncTask. It is exactly designed for this cases.
You can use AsyncTask multiple times just creating a new one like this new MyAsyncTask().execute(""); (source from here)
Also, is it an acceptable thing to call a thread from insider of another thread? (like the way I put stuff on UI Thread inside the new thread of mine)
runOnUiThread exists solely for that purpose. But there are usually much better ways (e.g. AsyncTask) so using this method is probably a bad idea.
my question is that whether this is the correct way to do this action or there is a more optimal way to do this?
You should not use a thread just to schedule future tasks. They are useful to execute something in parallel to the main thread but add lots of potential errors (try rotating the screen between it prints running..finished, could crash)
I would use a CountDownTimer in your case.
Or a Handler, examples e.g. here: Schedule task in android
From the provided code I assume that you want to perform an UI operation before and after your long mathematical computation. In such as #Andres suggested, AsyncTask is your best buy. It provides method onPreExecute, onPostExecute which runs on UI thread, and thus no need for explicitly calling runOnUiThread.
Key concepts :
You can't start an already started thread. This will return in an IllegalStateException. If you need to perform same task again, you should create a new instance.
If you find yourself creating several instances of a thread (even AsyncTask), since you need to run same task again and again, I would suggest you to use Thread Pool or simple Java Executor Service. Create a singleThread or may be pool and post your runnable onto executorService and it will take care of the rest.
Inter-Thread or Inter-Process communication is quite common requirement.
I have a java code that looks like this:
//UI thread
//Some code
Job j = new Job(jobName) {
#Override
public IStatus run(IProgressMonitor monitor) {
try {
//Some code
SomeFunc();
//Some code
return Status.OK_STATUS;
} catch(Exception e) {
}
finally {
}
}
};
j.schedule();
The problem is that the SomeFunc(); must be called from the UI thread.
I'm new to java, so can you please help me with showing the best methods for posting event to UI thread or calling a function of UI thread?
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
// your UI code
}
});
or you can use SwingWorker, its another choice:
http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html
In SWT, use Display.syncExec() as in this snippet or Display.asyncExec().
Its worth having a look at the Eclipse Jobs and Background Processing tutorial by Lars Vogel. There are good examples on how to run code on the UI thread from Eclipse Jobs.
So my program has multiple classes and after one of them has run, it'd like it so it appends the text area in the main class GUI with a 'finished' message
ta.append("Search Complete\n");
and this is the code that needs to complete
statCl.addActionListener(new ActionListener(){
public void actionPerformed (ActionEvent e) {
try {
ta.append("Searching...\n");
task.execute();
} catch (Exception IOE) {}
}
});
So it is in the class where task where actual code runs.
Any advice or help would be amazing, thanks.
If the task.execute() method doesn't start launch an operation in another thread, then the GUI will be freezed, and nothing will apear in the text area until the operation is finished. So you might just have
ta.append("Searching...\n");
task.execute();
ta.append("Finished");
If the operation is launched in a new thread, then this thread should append in the text area, but it should make sure this append is done in the event dispatch thread (EDT). Your code could thus look like this :
public class Task {
private JTextArea ta;
public Task(JTextArea ta) {
this.ta = ta;
}
public void execute() {
Thread t = new Thread(new Runnable() {
// perform the long operation
// at the end, update the text area, in the EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ta.append("finished");
}
});
}
t.start();
}
}
You might also look at SwingWorker, which is designed just for that (and other things like progress update). There is a code example in its class javadoc which does just what you're trying to do.
You should not be performing long-running task on EDT (event dispatching thread):
http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html
Swing does all it's work on EDT, so you should not block EDT, e.g. run intensive tasks on it. Note: all event handlers are executed on EDT.
So there are two challenges:
Run intensive tasks in a background thread.
Update GUI, which must be done on EDT.
Use SwingWorker to solve this two issues.
Say I have the following code:
import java.lang.InterruptedException;
import javax.swing.SwingWorker;
public class Test
{
private JDialog window;
public Test
{
// instantiate window
}
private class Task extends SwingWorker<Void, Void>
{
public Void doInBackground()
{
try { Thread.currentThread().sleep(5000); }
catch(InterruptedException e) {}
return null;
}
}
public void doTask()
{
Task task = new Task();
task.execute();
}
protected void process()
{
// update various GUI components here
}
public static void main(String args[])
{
Test t = new Test();
t.doTask();
System.out.println("done");
}
}
I need to wait until t.doTask() is done before printing out 'done', but I'm not sure exactly how. I know I should probably use join() here, but I need a thread to call it on, and I don't know how to get doInBackground()'s thread from where I need to call join(). Thanks for any help.
EDIT: Thanks for the responses. Unfortunately, get() and the like don't quite solve the problem. In my actual code, the SwingWorker also has an overridden process() function that updates a GUI window while the background thread is running. get() does stop 'done' from being printed till after doInBackground, but then the GUI doesn't update. I updated my sample code to reflect this, although now of course it won't compile.
Is there a way to get 'done' to print only once doInBackground is finished? Are the GUI update code and the 'done' statement on the same thread? Do I need to make a new thread?
Typically anything that needs to be done after a SwingWorker completes its background work is done by overriding the done() method in it. This method is called on the Swing event thread after completion, allowing you to update the GUI or print something out or whatever. If you really do need to block until it completes, you can call get().
NB. Calling get() within the done() method will return with your result immediately, so you don't have to worry about that blocking any UI work.
Calling get() will cause the SwingWorker to block.
From the Javadocs:
T get()
Waits if necessary for the computation to complete,
and then retrieves its result.
Your code will then look like:
public static void main(String args[])
{
Test t = new Test();
t.doTask();
t.get(); // Will block
System.out.println("done");
}
You can override the done() method, which is called when the doInBackground() is complete. The done() method is called on EDT. So something like:
#Override
protected void done() {
try {
super.get();
System.out.println("done");
//can call other gui update code here
} catch (Throwable t) {
//do something with the exception
}
}
Calling the get() method inside the done helps get back any exceptions that were thrown during the doInBackground, so I highly recommend it. SwingWorker uses Callable and Future internally to manage the background thread, which you might want to read up on instead of trying the join/yield approach.
In general, you must hold onto the SwingWorker until it finishes, which you can test by calling isDone() on it. Otherwise just call get() which makes it wait.