Do I need Threads for this?

G

Guest

I've got a method that copies files from one place to another. Each time the
file copies over it takes roughly 10 seconds per file (depending on how
large the file is).

In the meantime, I need my JPanel label to update with the current number of
files copied over so far.

I know from looking at the FAQs for this newsgroup that the GUI isn't
updating because it probably needs a new thread, but as a complete newbie to
threads, I'm wondering if I really need to learn about Threads to get my
relatively simple job done. My code simply runs in a for loop -

int count=0;

for (int i=0; i < 12; i++) {

mycopyOverFileMethod();
count++;
label.setText("Files Copied:" + count);

// Why is there no simple way in java just to put here something like :
// label.redraw();
// that avoids the need for threads??

}

Is there something simple I could do?

TIA
 
D

DRS.Usenet

I've got a method that copies files from one place to another. Each time the
file copies over it takes roughly 10 seconds per file (depending on how
large the file is).

In the meantime, I need my JPanel label to update with the current number of
files copied over so far.

Yes, you need threads, but it's not that hard. You make a class that
implements Runnable or extends Thread. Then you can do all ten files
at once, if you choose to!

--Dale--
 
G

Guest

Yes, you need threads, but it's not that hard. You make a class that
implements Runnable or extends Thread. Then you can do all ten files
at once, if you choose to!

Hmm, seems a bit awkward, but then Java seems that way sometimes...

Methodically, I can't understand how by splitting the work into two threads
its going to update the label at the time I need it to - won't it just split
into two and one process copy over the files in one thread all at once and
update the labels all at once in the other, meaning that there won't be any
real live interaction between the two? I guess I'm really saying that I
don't understand how the thread processes interact with each other...
 
O

Oliver Wong

Hmm, seems a bit awkward, but then Java seems that way sometimes...

Methodically, I can't understand how by splitting the work into two
threads its going to update the label at the time I need it to - won't it
just split into two and one process copy over the files in one thread all
at once and update the labels all at once in the other, meaning that there
won't be any real live interaction between the two? I guess I'm really
saying that I don't understand how the thread processes interact with each
other...

Presumably the thread which is doing the file-copying will update some
region in memory, recording it's progress. E.g. "Okay, I'm done with file
#4... now I'm done with #5... #6...", periodically releasing the CPU
(actually, once it tells the HD to start copying, it can immediately release
the CPU, because it has to wait for the HD to actually perform the reads and
the writes, only to be notified when the copy has finished, to schedule more
copies, and go back to sleep).

Meanwhile, the main thread will be scanning this region of memory and
see "Okay, so that thread is done with file #6. That means I need to change
the label to say 'Files copied: 6', and then release the CPU so that the
implicit thread that AWT/Swing creates has a chance to actually draw the
changes I've made to screen."

- Oliver
 
G

Guest

Presumably the thread which is doing the file-copying will update some
region in memory, recording it's progress. E.g. "Okay, I'm done with file
#4... now I'm done with #5... #6...", periodically releasing the CPU

Well yes - as in my original code -

mycopyOverFileMethod();
count++;
label.setText("Files Copied:" + count);

- this copies the file, when that's done, surely, the thread continues onto
count++ (recording in memory where its got to) and then sets the label.

Why does my label not update before going back around in the for loop to do
another file copy? Surely the processor is freed up to do the lines after
the mycopyOverFileMethod() before doing this method again? That's why I'm
confused!
 
C

Colin Miller

Try calling label.repaint(); after you set the text. Or if you're using
say a JFrame, try calling the JFrame's repaint(); method.

It's possible that even though you updated the component, it was never
painted to the screen.

~Colin
 
G

Guest

Try calling label.repaint(); after you set the text. Or if you're using
say a JFrame, try calling the JFrame's repaint(); method.

It's possible that even though you updated the component, it was never
painted to the screen.

Thanks for the suggestions - I'd already tried that - it just doesn't update
until the for loop gets to the end then it updates the label and obviously
not as intended (ie only updates it with the finishing number rather than
updating it with the current number as it goes along)

: (
 
C

Colin Miller

Ok, how about this. Create a new class of type Runnable (You'll need
to read up a bit on threading on this, and it's good to know anyway).
Inside of that class is where you will do your file copying. Also in
that class, have a variable to store whatever class holds your JPanel.
When creating an instance of this new class, pass it a reference to
that class for callback.

In your main class, or wherever you initiate the file copying, create
an instance of this new class and pass it "this" and set it to run. In
the class that does the file copying, after you finish copying a file,
you'll call a callback method to do the form update. Unfortunately, I'm
bad at explaining it.. I'll put some psudocode for you.

//Main Class
//Note: This will probably look different

public class MainClass {
public static void main(String[] args) {
JFrame = new JFrame();
/* ... all your other components and whatever you do to set your
screen */
}

public void buttonClick(Event e) {
//I'll assume you start file copying on a button click or something
FileCopying fc = new FileCopying();
fc.setCallback(this);
new Thread(fc).start();
}

public void updateCopies(int copies) {
label.setText("Files copied: " + copies);
}
}

//FileCopying class
public class FileCopying implements Runnable {
private MainClass callback;
/* whatever else you need for copying */

public void setCallback(MainClass cb) {
callback = cb;
}

public void run() {
for (int i=0; i < filesToBeCopied; i++) {
/* do your file copying here */
callback.updateCopies(i);
}
}
}


I hope that made some sense.. been a while since I did graphical work
and threads and all of that, but should help with some ideas hopefully.

~Colin
 
D

DRS.Usenet

Oliver said:
Presumably the thread which is doing the file-copying will update some
region in memory, recording it's progress.

Yeah, sorta, but when I think about doing things in Java, I try not to
think of memory regions. The idea has already been expressed above:
You pass a reference of "yourself" (ie "this") to the thread doing the
copy. Then you can do lots of stuff, like call a method in "yourself"
from the spun-off thread. And/or you can use "join" that waits for all
of your threads to complete. I didn't look at that example link, but I
imagine that would be a really good way to start (that is, unless
someone more energetic than I writes you a snippet or two... it really
wouldn't be many lines of code... like 15 maybe).

--Dale--

--Dale--
 
O

Oliver Wong

Well yes - as in my original code -

mycopyOverFileMethod();
count++;
label.setText("Files Copied:" + count);

- this copies the file, when that's done, surely, the thread continues
onto count++ (recording in memory where its got to) and then sets the
label.

Why does my label not update before going back around in the for loop to
do another file copy? Surely the processor is freed up to do the lines
after the mycopyOverFileMethod() before doing this method again? That's
why I'm confused!

I'm guessing your code is running in the EDT, the Event Dispatch Thread,
which AWT/Swing implicitly creates for you as soon as you use them for GUIs.

Because your code is running in the EDT, the EDT is not free to do other
stuff, like actually take the changes caused by label.setText(), and render
them onto the screen.

- Oliver
 
W

Wesley Hall

I've got a method that copies files from one place to another. Each time the
file copies over it takes roughly 10 seconds per file (depending on how
large the file is).

In the meantime, I need my JPanel label to update with the current number of
files copied over so far.

I know from looking at the FAQs for this newsgroup that the GUI isn't
updating because it probably needs a new thread, but as a complete newbie to
threads, I'm wondering if I really need to learn about Threads to get my
relatively simple job done. My code simply runs in a for loop -

int count=0;

for (int i=0; i < 12; i++) {

mycopyOverFileMethod();
count++;
label.setText("Files Copied:" + count);

// Why is there no simple way in java just to put here something like :
// label.redraw();
// that avoids the need for threads??

}

Is there something simple I could do?

TIA

OK, brace yourself :)

Yes, you will need threaded code to do this, but it should be reasonably
simple. You will need a basic understanding of threading in Java but it
is quite straightforward to do things like this.

Repaint will not work because it will simply queue the component for
repainting when the AWT thread (EDT) is available, and it wont be
available until your method returns.

Once you have gotten to grips with the basics of threading, create a
Runnable that does the file copy, and in between each file copy, change
your label text... however... there is something else you must understand...

You should not do any manipulation of visual components on any thread
but the AWT thread, visual components are not thread safe and may behave
strangely if you do not follow this rule. Fortunately, there is a class
called SwingUtilities that will allow you to pass tasks from your worker
thread to the AWT thread. SwingUtilities.invokeLater(Runnable) will
allow you to provide a task which will be run at some point (when AWT
thread gets around to it) and SwingUtilities.invokeAndWait(Runnable)
will pass the task on a block until it has been ran. You probably want
the latter in this case.

So, your code will look something similar to this...


private final JLabel statusLabel;

public void copyFiles()
{
Thread copyThread = new Thread(new Runnable()
{
public void run()
{
while(...)//there are more files to copy
{
//copy file
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
//Increment label value
}
});
}
}
}).start();
}



....but clean it up a bit :)
 
D

Daniel Dyer

Ok, how about this. Create a new class of type Runnable (You'll need
to read up a bit on threading on this, and it's good to know anyway).
Inside of that class is where you will do your file copying. Also in
that class, have a variable to store whatever class holds your JPanel.
When creating an instance of this new class, pass it a reference to
that class for callback.

In your main class, or wherever you initiate the file copying, create
an instance of this new class and pass it "this" and set it to run. In
the class that does the file copying, after you finish copying a file,
you'll call a callback method to do the form update. Unfortunately, I'm
bad at explaining it.. I'll put some psudocode for you.

<Snipped code>

The basic approach is sound, but you should not call any Swing methods
(with the exception of some text component methods that are explicitly
marked as thread-safe) from any thread other than the Event Dispatch
Thread. So instead of directly calling setText on the label, you need to
submit a Runnable to the Event Dispatch Thread via one of the methods in
SwingUtilities (invokeLater or invokeAndWait). The run method for this
Runnable will perform any necessary GUI updates.

Dan.
 
D

Daniel Dyer

OK, brace yourself :)

Yes, you will need threaded code to do this, but it should be reasonably
simple. You will need a basic understanding of threading in Java but it
is quite straightforward to do things like this.

Repaint will not work because it will simply queue the component for
repainting when the AWT thread (EDT) is available, and it wont be
available until your method returns.

Once you have gotten to grips with the basics of threading, create a
Runnable that does the file copy, and in between each file copy, change
your label text... however... there is something else you must
understand...

You should not do any manipulation of visual components on any thread
but the AWT thread, visual components are not thread safe and may behave
strangely if you do not follow this rule. Fortunately, there is a class
called SwingUtilities that will allow you to pass tasks from your worker
thread to the AWT thread. SwingUtilities.invokeLater(Runnable) will
allow you to provide a task which will be run at some point (when AWT
thread gets around to it) and SwingUtilities.invokeAndWait(Runnable)
will pass the task on a block until it has been ran. You probably want
the latter in this case.

The SwingWorker class provides a way of doing all this without having to
care too much about the underlying threads, which might be easier for the
OP if they are unfamiliar with threading.

You just have to sub-class SwingWorker and implement a couple of methods.
The base class deals with making sure they are invoked on the appropriate
threads.

SwingWorker has finally been added to the core API in Java 6
(http://java.sun.com/javase/6/docs/api/javax/swing/SwingWorker.html).
Unless you are on the bleeding edge, you're probably not using Java 6, but
you can download a copy of SwingWorker to use with older runtimes. It's
available from the SwingLabs project on java.net
(https://swingworker.dev.java.net/).

Dan.
 
K

Knute Johnson

Daniel said:
<Snipped code>

The basic approach is sound, but you should not call any Swing methods
(with the exception of some text component methods that are explicitly
marked as thread-safe) from any thread other than the Event Dispatch
Thread. So instead of directly calling setText on the label, you need
to submit a Runnable to the Event Dispatch Thread via one of the methods
in SwingUtilities (invokeLater or invokeAndWait). The run method for
this Runnable will perform any necessary GUI updates.

Dan.


--Daniel Dyer
http://www.uncommons.org

So this is how you do it in the simplest form. It looks really complex
but in reality it is very simple. Look at the code pieces and look at
the rules in comments.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class test {
//
// EventQueue.invokeLater(r) is used to run a Runnable piece of code on
// the event dispatch thread or EDT.
//
// new Thread(r).start() is used to run a Runnable piece of code on
a new
// thread.
//
// The Swing GUI must be created on the EDT
//
// Code that takes a lot of time cannot be run on the EDT or the update
// of the GUI will be prevented until it is done.
//
// All code that updates the GUI must be run on the EDT
//
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
final JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

final JLabel l = new JLabel(" ");
f.add(l,BorderLayout.NORTH);

JButton b = new JButton("Copy");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
Runnable r = new Runnable() {
public void run() {
try {
// the sleep is to simulate copying
files
for (int i=0; i<10; i++) {
final int fn = i;
Runnable r = new Runnable() {
public void run() {
l.setText("Copying File
#" +
Integer.toString(fn));
}
};
EventQueue.invokeLater(r);
// copy file here
Thread.sleep(1000);
}
Runnable r = new Runnable() {
public void run() {
l.setText("All files copied");
}
};
EventQueue.invokeLater(r);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
};
new Thread(r).start();
}
});
f.add(b,BorderLayout.SOUTH);

f.pack();
f.setVisible(true);
}
};
EventQueue.invokeLater(r);
}
}
 
N

Nigel Wade

Oliver said:
It's not super hard, but it's not super easy either. This tutorial
should get you started:
http://java.sun.com/docs/books/tutorial/essential/concurrency/

- Oliver

I was going to recommend the tutorial on Swing, which used to include very good
examples on how to perform tasks in threads, and how to use JProgressBar to
monitor progress. But this has now been replaced by a pretty useless one which
is completely dependent on Java 6 and the new SwingWorker. It seems that Sun no
longer consider that there is any other way to use threads in Swing...

If anyone knows where the old "Creating a GUI with JFC/Swing" can be found, I'd
be grateful for a link, because I used that tutorial extensively.
 
A

Alex Hunsley

Nigel said:
I was going to recommend the tutorial on Swing, which used to include very good
examples on how to perform tasks in threads, and how to use JProgressBar to
monitor progress. But this has now been replaced by a pretty useless one which
is completely dependent on Java 6 and the new SwingWorker. It seems that Sun no
longer consider that there is any other way to use threads in Swing...

If anyone knows where the old "Creating a GUI with JFC/Swing" can be found, I'd
be grateful for a link, because I used that tutorial extensively.

Hmmg, that's annoying! Could you find it on archive.org? (They cache
bits of the internet at various points, see the "waybackmachine" thingy.)
lex
 
A

Alex Hunsley

Thanks for the suggestions - I'd already tried that - it just doesn't update
until the for loop gets to the end then it updates the label and obviously
not as intended (ie only updates it with the finishing number rather than
updating it with the current number as it goes along)

It looks like you're doing the processing in the event despatch thread.
No GUI repainting can happen if you're hogging that thread. Let me
guess, you handle some GUI event (e.g. a button was pressed) by
launching into the given code, right? If so, that's your problem. You
need to fork off a different thread that contains the above code (for
copying each file and doing count++ etc. A call to repaint() may be
needed, but possibly not.)
lex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top