java - SwingWorker doens't run when calling Thread.sleep() in EDT thread
My SwingWorker is for counting time, it will update time every 1 second.
If the only responsibility of this worker is to update a label with the elapsed time, then a Swing Timer is a better candidate to do it. Let the SwingWorker doInBackground()
implementation do the heavy calculation and finally the done()
implementation may just stop the timer and display the final results.
Do not call Thread.sleep()
in the EDT because it will freeze the entire GUI event processing and you'll probably see just the final result without the intended periodic updates.
Update
Adding just a simple snippet to start with:
public void startCalculation() {
Timer timer = new Timer(1000, new ActionListener() {
AtomicInteger elapsedSeconds = new AtomicInteger();
@Override
public void actionPerformed(ActionEvent evt) {
label.setText(String.format("Elapsed time: %d seconds", elapsedSeconds.incrementAndGet()));
}
});
SwingWorker<Integer, Void> worker = new SwingWorker<Integer, Void>() {
@Override
protected Integer doInBackground() throws Exception {
// do heavy stuff
Thread.sleep(3000); // it is safe to "sleep" here because it's not executed in the EDT
return 1;
}
@Override
protected void done() {
timer.stop();
// update the GUI with the results here if it's required
}
};
timer.start();
worker.execute();
}
Thread.sleep() pauses JFrame before other statements
UI is not updated instantaneously.
When you call
flipCard(choice1, arg1);
flipCard(choice2, arg2);
I assume flipCard
updates the UI in some way. And your expected behaviour is that choice1
and choice2
cards will be flipped, wait 3 seconds, and flip them again. But the actual behaviour you get is that nothing happens until 3 seconds have passed, after 3 seconds, the cards flipped twice.
What you need to understand is that UI has a frame rate. When you call flipCard
, the card will not be flipped until the next frame. During the time between this frame and the next, Thread.sleep
is called so everything, including the frames, pause for 3 seconds. That's why the UI updated after 3 seconds' pause.
I suggest you to use javax.swing.Timer
or javax.swing.SwingWorker
. See here or here for more info.
Updating swing components correctly?
There are a number of important concepts you seem to be missing.
- Swing is an event driven environment. That means that there is no means (or at least only a very few) that you can "wait" for user input, typically, you just need to react to their interaction.
- Swing is driven by a single thread, known as the Event Dispatching Thread (AKA EDT). It is the responsibility of this thread to dispatch/process events coming into the application to the appropriate parts of the application so that they can take action.
- The repaint manager posts its update requests onto the EDT.
ANY action you take that stops the EDT from carrying out this work will make your application look like it's hung.
You must NEVER carry out any time consuming operations (such as I/O, loops or Thread#sleep
for example) on the EDT, doing so will make your application "pause", which is never pretty.
Have a read through Concurrency in Swing for more information.
Now, you have a number of choices. You could use a Thread
to "wait" in the background and turn the cards back or you could use a SwingWorker
or a javax.swing.Timer
.
The other problem you have, is that you should NEVER update any UI components from any Thread
other than the EDT. This means if you were to use a Thread
, you would become responsible for re-syncing that thread with the EDT. While not difficult, it just becomes messy.
SwingWorker
and javax.swing.Timer
have functionality that make this much easier.
Threads and SwingWorker
are great for performing background processing and would simply be overkill for this problem. Instead, a javax.swing.Timer
would fit perfectly here.
if (!Control.model.twoCardsTurned)
{
if ("unturn".equals(action))
{
new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
keypadArray[pos].setIcon(back);
keypadArray[Control.model.lastCard].setIcon(back);
System.out.println("Card: "+pos+" unset");
System.out.println("Card: "+Control.model.lastCard+" unset");
}
}).start();
}
}
This is a really simple example. You might like to put in some controls that will prevent the user from clicking anything until the timer fires, for example ;)
Timer won't stop when there is a Thread.sleep inside Java
This doesn't begin to make sense.
- You're scheduling a timer task to run every 1000 milliseconds.
- The task has 54 internal iterations which each display an image and in all cases except the last sleep for 200ms.
- Total time so far 10600 milliseconds plus however long it takes to display the images
- The timer will reschedule the task after 1000ms of that: meanwhile
- the task will cancel the timer on the last iteration.
So you will get:
- 53 images and 53 200ms sleeps
- a restart of the task after about 10% of that is complete
- a 54th image
- the timer gets cancelled.
So you get about ten or eleven iterations of the task, mostly in parallel.
I suggest you:
schedule the task at 200ms intervals, have it display the next sequential image every time it is invok d, wrapping around to the beginning or cancelling the timer or whatever you want when it gets to the last image, and
get rid of the internal loop.
Related Topics
How to Convert a String to Another Locale
How to Call Subclasses' Methods on a Superclass Object
Code for Changing the Color of Subtasks in Gantt Chart
Struts 2:There Is No Action Mapped for Namespace [/]
Graphics Rendering in Title Bar
How to Make Notepad to Save Text in Utf-8 Without the Bom
Using Jasperreports with a Relative Path
Date Format Parse Exception - "Eee Mmm Dd Hh:Mm:Ss Z Yyyy"
How to Display a Svg Byte Array as an Image in a Jasperreport