Using SwingWorker with FileVisitor not publishing processed info quickly; GUI hangs. I'd prefer it didn't and would like help with that problem.
Here's a brief outline of how I use SwingWorker with FileVisitor interface to search a Windows directory node for files matching user-specified criteria. :
public class Main
{
public static void main(String args[])
{
EventQueue.invokeLater( new Runnable() {
#Override public void run() {
gui = new GUI();
}});
}
}
//==============================================================
public class GUI extends JFrame
{
public GUI()
{
init();
createAndShowGUI();
}
private void init()
{
dftTableModel = new DefaultTableModel(0 , 4);
tblOutput = new JTable(dftTableModel);
tblOutput.setAutoResizeMode(AUTO_RESIZE_OFF);
scrTblOutput = new JScrollPane(tblOutput);
dftTableModel.setColumnIdentifiers(new Object[]{"Date", "Size", "Filename", "Path"});
EDIT HAD I ONLY INCLUDED THESE TWO LINES, PROBLEM MAY HAVE BEEN SOLVED IMMEDIATELY
tca = new tablecolumnadjuster.TableColumnAdjuster(tblOutput);
tca.setDynamicAdjustment(true);
}
private static void btnSearchActionPerformed(ActionEvent evt)
{
TASK task = new TASK();
task.execute();
}
}
}
//==============================================================
public class TASK extends SwingWorker<Void,String>
{
private class rowRec{
String date;
int size;
String filename;
String pathname;
private rowRec(String d, int s, String f, String p)
{
date = d;
size = s;
filename = f;
pathname = p;
}
}
FV fv;
TASK() { fv = new FV(); }
//-------------- inner class
class FV implements FileVisitor<Path>
{
// When walk begins, internal FileVisitor code makes this routine
// loop until no more files are found OR disposition = TERMINATE.
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException
{
if(f.getFileName().toString().toLowerCase().matches(fPatt.toLowerCase().trim()))
{
publish(s);
if(++k > parseInt(GUI.txtMaxMatches.getText()))
disposition = TERMINATE;
publish("Stopped at max. records specified");
}
return disposition;
}
}
//----------------
private void report(String s)
{
rowData = new rowRec(date, isize, filename, path);
dftTableModel.addRow(new Object[]{rowData.date, rowData.size, rowData.filename, rowData.pathname});
}
#Override
protected void process(List<String> chunks)
{
chunks.stream().
forEach
(
(chunk) ->
{
report(chunk);
}
);
kc += chunks.size();
System.out.println(kc); // *********************************
}
#Override
public Void doInBackground() throws Exception
{
disposition = FileVisitResult.CONTINUE;
Files.walkFileTree(GUI.p ,fv);
}
}
GUI starts a new instance of SwingWorker, whose job is to display (in a JTable) file info found by the instance of TASK that it starts. TASK instantiates FileVisitor, and walkFileTree begins. Every matching file found in the visitFile method is published for SwingWorker to process.
It works great most of the time, but if there is a ton of matching files, the GUI becomes unresponsive for several seconds; meanwhile, plenty of reading and displaying has occurred, and the UI is updated every few thousand file reads.
Despite using SwingWorker to fill the JTable in the background, apparently (I'm guessing) too much matching files info comes too fast to keep up.
Here's why I say that:
Even though visitFile has a counter which signals to TERMINATE, process apparently is far behind in adding records to the JTable. The println inside it shows that, as time passes, the number of chunks passed varies considerably, depending on rate of FileVisitor finding matches:
41
81
138
250
604
1146
...
1417
1497
1590
1670
1672
1676
1680
1682
1692
1730
1788
1794
1797
1801
1807
1820
1826
1829
1847
1933
2168
10001
After visitFile terminated, process had to send (10001-2168), or 7833 records to the JTable, and it took a long time, and the GUI was unresponsive much of the time. In fact, if the max. matches is (the ridiculous) 10,000, the program hangs for many MINUTES, but 10,000 records are in the JTable.
I don't know what to do about the unresponsiveness. I'd like to be able to hit the STOP button and have the program stop. Or be able to X (close) the window. No way.
Am I not using SwingWorker correctly? I can't make the tree walk be based on SwingWorker since there's no loop that I have available to me (it's internal).
P.S. While apparently hung, javaw is taking a solid 25% of CPU time and incrementing its memory allocation by about 16K per second, as process plods along, until it finally publishes the last chunk.
EDIT
I may have found help here.
But, gee, close??
I've highlighted my questions.
I don't know the solution to your problem, but I do know that this should not be done within the SwingWorker or its inner classes:
GUI.txtMaxMatches.getText())
You should get this information on the EDT and pass it into your SwingWorker via its constructor.
The responsiveness problem is solved in ONE LINE OF CODE.
// tca.setDynamicAdjustment(true);
Refer to edit in original question.
Related
basically, I have this code which was initially working with console i/o now I have to connect it to UI. It may be completely wrong, I've tried multiple things although it still ends up with freezing the GUI.
I've tried to redirect console I/O to GUI scrollpane, but the GUI freezes anyway. Probably it has to do something with threads, but I have limited knowledge on it so I need the deeper explanation how to implement it in this current situation.
This is the button on GUI class containing the method that needs to change this GUI.
public class GUI {
...
btnNext.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
controller.startTest(index, idUser);
}
});
}
This is the method startTest from another class which contains instance of Question class.
public int startTest() {
for (int i = 0; i < this.numberofQuestions; i++) {
Question qt = this.q[i];
qt.askQuestion(); <--- This needs to change Label in GUI
if(!qt.userAnswer()) <--- This needs to get string from TextField
decreaseScore(1);
}
return actScore();
}
askQuestion method:
public void askQuestion() {
System.out.println(getQuestion());
/* I've tried to change staticaly declared frame in GUI from there */
}
userAnswer method:
public boolean userAnswer() {
#SuppressWarnings("resource")
Scanner scanner = new Scanner(System.in);
if( Objects.equals(getAnswer(),userInput) ) {
System.out.println("Correct");
return true;
}
System.out.println("False");
return false;
}
Thanks for help.
You're correct in thinking that it related to threads.
When you try executing code that will take a long time to process (eg. downloading a large file) in the swing thread, the swing thread will pause to complete execution and cause the GUI to freeze. This is solved by executing the long running code in a separate thread.
As Sergiy Medvynskyy pointed out in his comment, you need to implement the long running code in the SwingWorker class.
A good way to implement it would be this:
public class TestWorker extends SwingWorker<Integer, String> {
#Override
protected Integer doInBackground() throws Exception {
//This is where you execute the long running
//code
controller.startTest(index, idUser);
publish("Finish");
}
#Override
protected void process(List<String> chunks) {
//Called when the task has finished executing.
//This is where you can update your GUI when
//the task is complete or when you want to
//notify the user of a change.
}
}
Use TestWorker.execute() to start the worker.
This website provides a good example on how to use
the SwingWorker class.
As other answers pointed out, doing heavy work on the GUI thread will freeze the GUI. You can use a SwingWorker for that, but in many cases a simple Thread does the job:
Thread t = new Thread(){
#Override
public void run(){
// do stuff
}
};
t.start();
Or if you use Java 8+:
Thread t = new Thread(() -> {
// do stuff
});
t.start();
I have been working with SwingWorkers for a while and have ended up with a strange behavior, at least for me. I clearly understand that due to performance reasons several invocations to publish() method are coallesced in one invocation. It makes perfectly sense to me and I suspect SwingWorker keeps some kind of queue to process all that calls.
According to tutorial and API, when SwingWorker ends its execution, either doInBackground() finishes normally or worker thread is cancelled from the outside, then done() method is invoked. So far so good.
But I have an example (similar to shown in tutorials) where there are process() method calls done after done() method is executed. Since both methods execute in the Event Dispatch Thread I would expect done() be executed after all process() invocations are finished. In other words:
Expected:
Writing...
Writing...
Stopped!
Result:
Writing...
Stopped!
Writing...
Sample code
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Demo {
private SwingWorker<Void, String> worker;
private JTextArea textArea;
private Action startAction, stopAction;
private void createAndShowGui() {
startAction = new AbstractAction("Start writing") {
#Override
public void actionPerformed(ActionEvent e) {
Demo.this.startWriting();
this.setEnabled(false);
stopAction.setEnabled(true);
}
};
stopAction = new AbstractAction("Stop writing") {
#Override
public void actionPerformed(ActionEvent e) {
Demo.this.stopWriting();
this.setEnabled(false);
startAction.setEnabled(true);
}
};
JPanel buttonsPanel = new JPanel();
buttonsPanel.add(new JButton(startAction));
buttonsPanel.add(new JButton(stopAction));
textArea = new JTextArea(30, 50);
JScrollPane scrollPane = new JScrollPane(textArea);
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(scrollPane);
frame.add(buttonsPanel, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void startWriting() {
stopWriting();
worker = new SwingWorker<Void, String>() {
#Override
protected Void doInBackground() throws Exception {
while(!isCancelled()) {
publish("Writing...\n");
}
return null;
}
#Override
protected void process(List<String> chunks) {
String string = chunks.get(chunks.size() - 1);
textArea.append(string);
}
#Override
protected void done() {
textArea.append("Stopped!\n");
}
};
worker.execute();
}
private void stopWriting() {
if(worker != null && !worker.isCancelled()) {
worker.cancel(true);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().createAndShowGui();
}
});
}
}
SHORT ANSWER:
This happens because publish() doesn't directly schedule process, it sets a timer which will fire the scheduling of a process() block in the EDT after DELAY. So when the worker is cancelled there is still a timer waiting to schedule a process() with the data of the last publish. The reason for using a timer is to implement the optimization where a single process may be executed with the combined data of several publishes.
LONG ANSWER:
Let's see how publish() and cancel interact with each other, for that, let us dive into some source code.
First the easy part, cancel(true):
public final boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}
This cancel ends up calling the following code:
boolean innerCancel(boolean mayInterruptIfRunning) {
for (;;) {
int s = getState();
if (ranOrCancelled(s))
return false;
if (compareAndSetState(s, CANCELLED)) // <-----
break;
}
if (mayInterruptIfRunning) {
Thread r = runner;
if (r != null)
r.interrupt(); // <-----
}
releaseShared(0);
done(); // <-----
return true;
}
The SwingWorker state is set to CANCELLED, the thread is interrupted and done() is called, however this is not SwingWorker's done, but the future done(), which is specified when the variable is instantiated in the SwingWorker constructor:
future = new FutureTask<T>(callable) {
#Override
protected void done() {
doneEDT(); // <-----
setState(StateValue.DONE);
}
};
And the doneEDT() code is:
private void doneEDT() {
Runnable doDone =
new Runnable() {
public void run() {
done(); // <-----
}
};
if (SwingUtilities.isEventDispatchThread()) {
doDone.run(); // <-----
} else {
doSubmit.add(doDone);
}
}
Which calls the SwingWorkers's done() directly if we are in the EDT which is our case. At this point the SwingWorker should stop, no more publish() should be called, this is easy enough to demonstrate with the following modification:
while(!isCancelled()) {
textArea.append("Calling publish\n");
publish("Writing...\n");
}
However we still get a "Writing..." message from process(). So let us see how is process() called. The source code for publish(...) is
protected final void publish(V... chunks) {
synchronized (this) {
if (doProcess == null) {
doProcess = new AccumulativeRunnable<V>() {
#Override
public void run(List<V> args) {
process(args); // <-----
}
#Override
protected void submit() {
doSubmit.add(this); // <-----
}
};
}
}
doProcess.add(chunks); // <-----
}
We see that the run() of the Runnable doProcess is who ends up calling process(args), but this code just calls doProcess.add(chunks) not doProcess.run() and there's a doSubmit around too. Let's see doProcess.add(chunks).
public final synchronized void add(T... args) {
boolean isSubmitted = true;
if (arguments == null) {
isSubmitted = false;
arguments = new ArrayList<T>();
}
Collections.addAll(arguments, args); // <-----
if (!isSubmitted) { //This is what will make that for multiple publishes only one process is executed
submit(); // <-----
}
}
So what publish() actually does is adding the chunks into some internal ArrayList arguments and calling submit(). We just saw that submit just calls doSubmit.add(this), which is this very same add method, since both doProcess and doSubmit extend AccumulativeRunnable<V>, however this time around V is Runnable instead of String as in doProcess. So a chunk is the runnable that calls process(args). However the submit() call is a completely different method defined in the class of doSubmit:
private static class DoSubmitAccumulativeRunnable
extends AccumulativeRunnable<Runnable> implements ActionListener {
private final static int DELAY = (int) (1000 / 30);
#Override
protected void run(List<Runnable> args) {
for (Runnable runnable : args) {
runnable.run();
}
}
#Override
protected void submit() {
Timer timer = new Timer(DELAY, this); // <-----
timer.setRepeats(false);
timer.start();
}
public void actionPerformed(ActionEvent event) {
run(); // <-----
}
}
It creates a Timer that fires the actionPerformed code once after DELAY miliseconds. Once the event is fired the code will be enqueued in the EDT which will call an internal run() which ends up calling run(flush()) of doProcess and thus executing process(chunk), where chunk is the flushed data of the arguments ArrayList. I skipped some details, the chain of "run" calls is like this:
doSubmit.run()
doSubmit.run(flush()) //Actually a loop of runnables but will only have one (*)
doProcess.run()
doProcess.run(flush())
process(chunk)
(*)The boolean isSubmited and flush() (which resets this boolean) make it so additional calls to publish don't add doProcess runnables to be called in doSubmit.run(flush()) however their data is not ignored. Thus executing a single process for any amount of publishes called during the life of a Timer.
All in all, what publish("Writing...") does is scheduling the call to process(chunk) in the EDT after a DELAY. This explains why even after we cancelled the thread and no more publishes are done, still one process execution appears, because the moment we cancel the worker there's (with high probability) a Timer that will schedule a process() after done() is already scheduled.
Why is this Timer used instead of just scheduling process() in the EDT with an invokeLater(doProcess)? To implement the performance optimization explained in the docs:
Because the process method is invoked asynchronously on the Event
Dispatch Thread multiple invocations to the publish method might occur
before the process method is executed. For performance purposes all
these invocations are coalesced into one invocation with concatenated
arguments.
For example:
publish("1");
publish("2", "3");
publish("4", "5", "6");
might result in:
process("1", "2", "3", "4", "5", "6")
We now know that this works because all the publishes that occur within a DELAY interval are adding their args into that internal variable we saw arguments and the process(chunk) will execute with all that data in one go.
IS THIS A BUG? WORKAROUND?
It's hard to tell If this is a bug or not, It might make sense to process the data that the background thread has published, since the work is actually done and you might be interested in getting the GUI updated with as much info as you can (if that's what process() is doing, for example). And then it might not make sense if done() requires to have all the data processed and/or a call to process() after done() creates data/GUI inconsistencies.
There's an obvious workaround if you don't want any new process() to be executed after done(), simply check if the worker is cancelled in the process method too!
#Override
protected void process(List<String> chunks) {
if (isCancelled()) return;
String string = chunks.get(chunks.size() - 1);
textArea.append(string);
}
It's more tricky to make done() be executed after that last process(), for example done could just use also a timer that will schedule the actual done() work after >DELAY. Although I can't think this is would be a common case since if you cancelled It shouldn't be important to miss one more process() when we know that we are in fact cancelling the execution of all the future ones.
Having read DSquare's superb answer, and concluded from it that some subclassing would be needed, I've come up with this idea for anyone who needs to make sure all published chunks have been processed in the EDT before moving on.
NB I tried to write it in Java rather than Jython (my language of choice and officially the best language in the world), but it is a bit complicated because, for example, publish is final, so you'd have to invent another method to call it, and also because you have to (yawn) parameterise everything with generics in Java.
This code should be understandable by any Java person: just to help, with self.publication_counter.get(), this evaluates to False when the result is 0.
# this is how you say Worker... is a subclass of SwingWorker in Python/Jython
class WorkerAbleToWaitForPublicationToFinish( javax.swing.SwingWorker ):
# __init__ is the constructor method in Python/Jython
def __init__( self ):
# we just add an "attribute" (here, publication_counter) to the object being created (self) to create a field of the new object
self.publication_counter = java.util.concurrent.atomic.AtomicInteger()
def await_processing_of_all_chunks( self ):
while self.publication_counter.get():
time.sleep( 0.001 )
# fully functional override of the Java method
def process( self, chunks ):
for chunk in chunks:
pass
# DO SOMETHING WITH EACH CHUNK
# decrement the counter by the number of chunks received
# NB do this AFTER dealing with the chunks
self.publication_counter.addAndGet( - len( chunks ) )
# fully functional override of the Java method
def publish( self, *chunks ):
# increment the counter by the number of chunks received
# NB do this BEFORE publishing the chunks
self.publication_counter.addAndGet( len( chunks ))
self.super__publish( chunks )
So in your calling code, you put something like:
engine.update_xliff_task.get()
engine.update_xliff_task.await_processing_of_all_chunks()
PS the use of a while clause like this (i.e. a polling technique) is hardly elegant. I looked at the available java.util.concurrent classes such as CountDownLatch and Phaser (both with thread-blocking methods), but I don't think either would suit for this purpose...
later
I was interested enough in this to tweak a proper concurrency class (written in Java, found on the Apache site) called CounterLatch. Their version stops the thread at await() if a value of an AtomicLong counter is reached. My version here allows to you to either to do that, or the opposite: to say "wait until the counter reaches a certain value before lifting the latch":
NB use of AtomicLong for signal and AtomicBoolean for released: because in the original Java they use the volatile keyword. I think using the atomic classes will achieve the same purpose.
class CounterLatch():
def __init__( self, initial = 0, wait_value = 0, lift_on_reached = True ):
self.count = java.util.concurrent.atomic.AtomicLong( initial )
self.signal = java.util.concurrent.atomic.AtomicLong( wait_value )
class Sync( java.util.concurrent.locks.AbstractQueuedSynchronizer ):
def tryAcquireShared( sync_self, arg ):
if lift_on_reached:
return -1 if (( not self.released.get() ) and self.count.get() != self.signal.get() ) else 1
else:
return -1 if (( not self.released.get() ) and self.count.get() == self.signal.get() ) else 1
def tryReleaseShared( self, args ):
return True
self.sync = Sync()
self.released = java.util.concurrent.atomic.AtomicBoolean() # initialised at False
def await( self, *args ):
if args:
assert len( args ) == 2
assert type( args[ 0 ] ) is int
timeout = args[ 0 ]
assert type( args[ 1 ] ) is java.util.concurrent.TimeUnit
unit = args[ 1 ]
return self.sync.tryAcquireSharedNanos(1, unit.toNanos(timeout))
else:
self.sync.acquireSharedInterruptibly( 1 )
def count_relative( self, n ):
previous = self.count.addAndGet( n )
if previous == self.signal.get():
self.sync.releaseShared( 0 )
return previous
So my code now looks like this:
In the SwingWorker constructor:
self.publication_counter_latch = CounterLatch()
In SW.publish:
self.publication_counter_latch.count_relative( len( chunks ) )
self.super__publish( chunks )
In the thread waiting for chunk processing to stop:
worker.publication_counter_latch.await()
The plan:
I created a little PreferenceActivity (don't hate me, I'm supporting API 10 and up) and need to display the current usage of local storage data by my app. I did this using a specialized class (a pretty big one, as of the moment) that handles all file operations (it's called FileOperations.java for a reason). Inside this class file there is a method getSize(File file) {...} which does just that. It gets the size of a file (or folder) with this little piece of code:
public long getSize(File file) {
long size = 0;
if(file.isDirectory()) {
for(File child : file.listFiles()) {
size += getSize(child);
}
}
size = file.length();
return size;
}
The general idea was to use this in a background Thread so it doesn't clog the UI even the slightest bit. (I am really annoyed by lagging apps and suffer from them daily)
The problem:
This works just fine. However, as soon as I purge the folder the app stores it's data in using this beauty:
private void purgeLocalStorage() {
new Thread(new Runnable() {
#Override
public void run() {
Looper.prepare();
Log.i("ActivityPrefsLocalStorage.purgeLocalStorage.Thread.Runnable.run", "Started to run");
final String directory = context.getResources().getString(R.string.app_name);
final String usedData = context.getResources().getString(R.string.ActivityPrefsLocalStorage_usedData);
final File file = new File(Environment.getExternalStorageDirectory()+"/"+directory);
final FileOperations FO = new FileOperations(context);
Log.i("ActivityPrefsLocalStorage.purgeLocalStorage.Thread.Runnable.run", "deleting folder: "+file);
if(FO.delete(file)) {
Log.i("ActivityPrefsLocalStorage.purgeLocalStorage.Thread.Runnable.run", file+" deleted");
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(context, R.string.ActivityPrefsLocalStorage_deleteSucces, Toast.LENGTH_SHORT).show();
setTotalLocalDataTexts(usedData+" "+context.getResources().getString(R.string.pref_totalData_default), "");
getUsedStorage();
}
});
} else {
Log.e("ActivityPrefsLocalStorage.purgeLocalStorage.Thread.Runnable.run", "could not delete "+file);
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(context, R.string.ActivityPrefsLocalStorage_deleteError, Toast.LENGTH_SHORT).show();
}
});
}
}
}).start();
}
Things hit the fan...
See, the problem is that my method for reading the size of the folder does not want to function properly when called by the previous method.
Here's a snippet:
private void getUsedStorage() {
new Thread(new Runnable() {
#Override
public void run() {
Log.i("ActivityPrefsLocalStorage.getUsedStorage.Thread.Runnable.run", "Started to run");
final String directory = context.getResources().getString(R.string.app_name);
final String usedData = context.getResources().getString(R.string.ActivityPrefsLocalStorage_usedData);
final File file = new File(Environment.getExternalStorageDirectory()+"/"+directory);
final FileOperations FO = new FileOperations(context);
final DataUsage DU = new DataUsage(context);
Log.i("ActivityPrefsLocalStorage.getUsedStorage.Thread.Runnable.run", "Checking filesize of folder: "+file);
long fileSize = FO.getSize(file);
String usedUnits = DU.getUnit(fileSize, true, false, false);
String usedBytes = DU.getUnit(fileSize, true, true, true);
Log.i("ActivityPrefsLocalStorage.getUsedStorage.Thread.Runnable.run", "filesize of "+file+": "+usedUnits);
setTotalLocalDataTexts(usedData+" "+usedUnits, usedBytes);
}
}).start();
}
However, a quick and easy workaround would be to place it on the UI thread like so:
...blabla code you already read above.
long fileSize = FO.getSize(file);
String usedUnits = DU.getUnit(fileSize, true, false, false);
String usedBytes = DU.getUnit(fileSize, true, true, true);
Log.i("ActivityPrefsLocalStorage.getUsedStorage.Thread.Runnable.run", "filesize of "+file+": "+usedUnits);
runOnUiThread(new Runnable() {
#Override
public void run() {
setTotalLocalDataTexts(usedData+" "+usedUnits, usedBytes);
}
});
}
}).start();
}
And that's where it starts getting interesting. I cannot use non-finals inside the new Runnable(), and I cannot make them final since I want the value to update and not remain stuck at eg. 32MiB (while it has just been purged).
Possible fixes:
I should man up and just use a final. The user will understand they need to refresh the page manually. (oh no...)
Hire... erm. Extend an AsyncTask<Void, Void, Void> to do the work.
My ideal fix:
Someone giving me an awesome snippet of code for free that does all the magic. no, seriously though, I would really appreciate anything apart from my list of possible fixes. There has to be some way to pass the new Runnable() a variable without creating classes and implementing the entire universe? Or is that what I am trying to achieve really a new thing?
TL;DR:
Things go wrong as soon as I call getUsedStorage() from within a new Runnable(). This function is also a background task inside a Runnable, but updates the UI using a private void function that sets it. It only passes variables to this function. and then things fly off the handle(r).
Edit: grammar.
Edit2: Also a pretty interesting thing to note here, I used something similar in another PreferenceActivity, and that one works. (but that one does not update at the press of a button that calls another private something functionName() {new Thread(new Runnable() {public void run() {...});})
There are a couple of ways to use non-finals inside of a Runnable or other enclosed classes.
The first is to change your variables to be members of an enclosing class. This will allow you to use the variables inside the Runnable. An example follows:
public class Foo {
private long time;
public void run() {
time = System.currentTimeMillis();
new Thread(new Runnable() {
#Override
public void run() {
time += 1;
System.out.println("Our current time is: " + time);
}
});
}
}
The other option, and it is quite hacky, is to use a final array with a length of 1. An example of that follows:
public class Foo {
public void run() {
final long[] time = { System.currentTimeMillis() };
new Thread(new Runnable() {
#Override
public void run() {
time[0] += 1;
System.out.println("Our current time is: " + time[0]);
}
});
}
}
As gonemad16 on reddit.com/r/androiddev pointed out, my issue had nothing to do with final vs non final. That was not the reason I was getting an old value. All my variables are given a value, sent to setTotalLocalDataTexts and then go out of scope... nothing is updating their values.. so there is no harm in them being final and no benefit to them being non final...
It was an an issue in getSize()
I thought I had a correct loop there using if file.isDirectory() {...}. It created a directory tree and executed itself using the children it has found. When all items have been scanned the value returns to the function calling it.
This was working just fine for me while I was still running all of my code on the ui thread. Everything was slow. But it worked.
However, I forgot that I removed a very crucial ...} else {...
I believe I removed that one because it caused a stack overflow at some point, so I removed it and I guess forgot to put it back...
And here I was thinking my first SO question wouldn't be a noobish question...
There are two windows: a GUI for user input and Output window for list of filenames found. Execution must be user-stoppable via a keypress and must leave both windows open because the program processes subdirectories, so it can run a long time, possibly stepping thru 100_000 files, either producing tons of output or none at all, depending on how user's filename pattern matches files encountered in the selected starting node.
Here's my question:
How do I look for a keypress (e.g., ESC or CTRL-C) to allow user to terminate? (Clicking red X isn't an option since that closes windows; user needs to see what's been found before termination. Doing so does not close either window anyway since all buttons are disabled once tree walk begins.)
I've tried putting keyListeners in several places, but once the "Start" button is clicked, all the swing components are disabled.
This seems like such a common situation that I'm surprised I can't find any textbook, thread, or Google info that directly answers the question. So I'm afraid it's not gonna be at all easy. That would be no surprise. I may have found a clue here but I can't get it to compile and the link contained there doesn't lead to that code snippet.
The search begins when the Search button is clicked:
private void jbSearchActionPerformed(ActionEvent evt) {
SearchyGUI.doIt();
}
The doIt() method walks the directory tree by an extension of SimplefileVisitor:
public class OverriddenFileVisitor extends SimpleFileVisitor<Path> {
...
}
public static void doIt(){
try {
visitor = new OverriddenFileVisitor();
info.setVisible(true);
Files.walkFileTree(SearchyGUI.p , visitor);
}
catch (Exception e) { }
}
}
Output is written to jTextArea1 via the report() method:
public static void report(String s){
Output.jTextArea1.append(s + "\n");
}
This is done primarily in the visitFile() method of SimpleFileVisitor:
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
report(foundkt + "--" + f.getFileName().toString());
return FileVisitResult.CONTINUE;
}
Here's the main class:
public class SearchyGUI {
static Output info;
static Path p ;
static FileVisitor visitor ;
static GUI gui
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
gui = new GUI();
gui.setVisible(true);
}
});
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
info = new Output();
}
});
}
The problem is you are hogging the GUI thread, so the GUI thread can't process any events originating from the user.
You need to create a new Thread and do the work in there. Then, to display output from that thread, you can use SwingUtilities.invokeLater or something like that.
The Key Bindings API is probably the best choice for monitoring key strokes.
I would also add a [Cancel] button to the UI, which shared the same action...
public class CancelAction extends AbstractAction {
public CancelAction() {
putValue(NAME, "Cancel");
}
public void actionPerformed(ActionEvent evt) {
// Perform the cancel operation...
}
}
Then some where else in your code...
CancelAction cancelAction = new CancelAction();
JButton cancelButton = new JButton(cancelAction);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Cancel");
am.put("Cancel", am);
Now the other problem you're going to have is the fact that you look like you are running a long running task within the context of the Event Dispatching Thread. This is going to prevent your program from being able to update the UI or allow the user to interact with the UI.
If you need to make changes to the UI (ie, show the output of the file processing), you should try a SwingWorker.
The main reason being is that it allows you to execute the long running task in another thread, but provides the mechanism for re-syncing updates back to the EDT, where it is safe to make changes to the UI.
Take a look at Concurrency in Swing for more details.
Regardless of which direction you take, you're going to need to supply a reference to the object that is carrying out the task and provide some kind of "cancel" flag, which the "task" object will need to monitor
The way I had left this program last night was unsatisfactory since Exit resulted in user not being able to see the output so far displayed (it could be useful). So I established window listeners and used the close event to set a boolean aborted to true to prevent further output to the window, but the thread kept running, which led to intermittent problems if another search was started before the thread ended.
Here's how I fixed it.
The FileVisitor interface has 4 methods to implement to walk the tree--two for each file visited, two for each directory. Each returns a FileVisitResult which is normally FileVisitResult.CONTINUE. By changing the return value to FileVisitResult.TERMINATE in the file visitor thread, it terminates appropriately! That is, I set a flag that the thread could check and take appropriate action, which is exactly what #MadProgrammer suggested.
public static FileVisitResult disposition = FileVisitResult.CONTINUE;
...
private static void report(String s){
if (! aborted)
try{
Output.jTextArea1.append(s + "\n");
}
catch (Exception e){
aborted = true ;
disposition = FileVisitResult.TERMINATE;
}
}
...
#Override
public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException {
f1 = new File(f.getParent().toString() + "\\" + f.getFileName().toString());
long filesize = f1.length();
report(f.getFileName().toString() + "\t found in " + f.getParent().toString());
return disposition;
}
I am one happy camper! Thank you BOTH for your ideas and input.
Well, I made it stop. I guess if you wander the woods long enough you'll find a gnome. I read Robin's hint last week and sort of gave up. Then I read some more and more. And then more. But Robin assured me that gnomes DO exist in these here woods!
The code I used was a modification of some I found for a MatLab/Java app. (Why'd I even look at it?? Best apparent Google hint.)
I made the "file visitor" (directory tree walker component) startable as a thread as Robin advised:
public class OverriddenFileVisitor extends SimpleFileVisitor<Path> implements Runnable{
// ................................................................^^^^^^^^^^^^^^^^^^^
In doIt() I made a couple of changes, moving the lines that process the directory to the now-runnable class and started the file visitor as its own thread in doIt():
public static void doIt(){
try {
new OverriddenFileVisitor().startTh();
//^^^^^^^^^^
//(moved) Files.walkFileTree(SearchyGUI.p , visitor);
...
I added the new method in the previous line to OverriddenFileVisitor class: (This is the main part of the MatLab/Java code that made sense to me so I used and modified it.)
public void startTh() {
Thread t = new Thread(this);
t.start();
}
And I inserted the overridden run() method for the class:
public void run() {
try {
Files.walkFileTree(SearchyGUI.p , this); // Used to be in doIt().
}
catch (IOException ex) { }
}
It ran and gave correct results and stopped when I hit Exit button, which "became" enabled after revising the file visitor to run in its own thread, which is what #Robin Green was saying. I almost feel like I know what I've done.
P.S. Note that I already was able to get my output via invokeLater()--last several lines of original question.
It's not finished but it's much more satisfactory.
Context: I am reading data from a serial port at 115.2 Kbaud. The read data is printed using a PrintWriter that I then have appending to a JTextArea.
Everything works well, but the text in the JTextArea does not appear until the method sending the stream from the serial port to my PrintWriter finishes. I'd like it to display closer to real-time, as I will at times be receiving upwards of 20-30 MB of text at a time, and how the general flow of text changes as the program executes would be valuable.
I am using the PrintWriter to JTextArea method here. I think the solution probably has to do with Threads and PipedWriter/PipedReader, but every attempt I've made to implement that has failed miserably.
Thank you for your help.
//code calling method; VerifierReader does not inherit from Reader
//or any such class. it's wholly homegrown. I send it the PrintWriter
//as out, telling it to output there
verifierInstance=new VerifierReader("COM3", verifierOutputLocString.getText());
verifierInstance.setSysOutWriter(out);
verifierInstance.readVerifierStream();
// and the relevant code from VerifierReader
public void setSysOutWriter (PrintWriter outWriter) {
sysOutWriter=new PrintWriter(outWriter);
}
public void readVerifierStream() throws SerialPortException,
InterruptedException{
try{
sysOutWriter.println("Listening for verifier...");
//sysOutWriter.flush();
verifierPort.addEventListener(new verifierListener());
lastReadTimer=System.currentTimeMillis();
while(verifierPort.isOpened()) {
Thread.sleep(1000);
//System.out.println(timeOut);
if( ((long)(System.currentTimeMillis()-lastReadTimer))>timeOut){
sysOutWriter.println("Finished");
verifierPort.removeEventListener();
verifierPort.closePort();
}
}
}
finally {
if (verifierPort.isOpened()) {
verifierPort.closePort();
}
bfrFile.close();
}
}
private class verifierListener implements SerialPortEventListener{
String outBuffer;
public void serialEvent(SerialPortEvent event) {
if(event.isRXCHAR()){//If data is available
timeOut=200;
lastReadTimer=System.currentTimeMillis();
if(event.getEventValue() > 0){//Check bytes count in the input buffer
try {
byte[] buffer = verifierPort.readBytes(event.getEventValue());
outBuffer=new String(buffer);
bfrFile.print(outBuffer);
sysOutWriter.print(outBuffer);
//bfrFile.flush();
//sysOutWriter.flush();
}
catch (SerialPortException ex) {
sysOutWriter.println(ex);
}
}
}
}
}
Edit:
I've attempted what was recommended below, and have made the following changes:
private class VerifierTask extends SwingWorker<Void, String> {
public VerifierTask() throws IOException, SerialPortException, InterruptedException{
verifierInstance= new VerifierReader(streamReader);
verifierInstance.setReaderIO("COM3", verifierOutputLocString.getText());
verifierInstance.readVerifierStream();
}
#Override
protected Void doInBackground() throws IOException{
int charItem;
char[] charBuff = new char[10];
String passString;
while ((charItem = streamReader.read(charBuff, 0, 10)) !=-1) {
passString = new String(charBuff);
publish(passString);
}
return null;
}
#Override
protected void process(List<String> outList) {
for (String output : outList) {
outputArea.append(output);
}
}
}
was added, and I changed my button to immediately invoke a new instance of the VerifierTask class, in addition to making VerifierReader implement a PipedWriter for output (with all of that being Strings).
I'm not sure what I'm doing wrong here. When this code is executed the Java process just freezes indefinitely.
Am I assuming correctly that a VerifierReader created in any VerifierTask thread is tied to that thread, and thus my thread.sleep and while(true) statements no longer pose a problem?
Don't call Thread.sleep or do while (true) on the main Swing event thread, the EDT. Ever. Instead do this sort of thing in a background thread such as one provided via a SwingWorker. You would use the publish/process method pair to get intermediate results to your JTextArea.
For more on this, please check out the tutorial: Concurrency in Swing.