I tried to find the answer to my problem here on SO, but due to their abundance and diversity I got somewhat confused. Here is my question: my app compares two files and prints out the result in a Swing.JTextPane
. I call the code that processes the files with a button and to avoid hanging the UI I process each pair of files with a SwingWorker
. Here is its code:
class ProcessAndPrintTask extends SwingWorker<Void, Void> {
private Report report;
Integer reportResult;
ProcessAndPrintTask(Report report) {
this.report = report;
reportResult = null;
}
@Override
protected Void doInBackground() {
try {
reportResult = report.getComparator().compareTwoFiles(new FileInputStream(new File(pathToReportsA + report.getFilename())),
new FileInputStream(new File(pathToReportsB + report.getFilename())));
}
catch (IOException ex) {
ex.printStackTrace();
}
return null;
}
@Override
protected void done() {
String message = report.getFilename() + ": ";
if (reportResult != null) {
switch (reportResult) {
case 1:
StyleConstants.setBackground(style, Color.GREEN);
try {
doc.insertString(doc.getLength(), message + "MATCH\n", style);
}
catch (BadLocationException ex) {ex.printStackTrace();}
break;
case 0:
StyleConstants.setBackground(style, Color.RED);
try {
doc.insertString(doc.getLength(), message + "NO MATCH\n\n", style);
try {
for (String s : report.getComparator().getDifferences(
new FileInputStream(new File(pathToReportsA + report.getFilename())),
new FileInputStream(new File(pathToReportsB + report.getFilename())))) {
doc.insertString(doc.getLength(), s + "\n", style);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
catch (BadLocationException ex) {ex.printStackTrace();}
break;
case -1:
StyleConstants.setBackground(style, Color.CYAN);
try {
doc.insertString(doc.getLength(), message + "BOTH FILES EMPTY\n", style);
}
catch (BadLocationException ex) {ex.printStackTrace();}
break;
default:
StyleConstants.setBackground(style, Color.ORANGE);
try {
doc.insertString(doc.getLength(), message + "PROBLEM\n", style);
}
catch (BadLocationException ex) {ex.printStackTrace();}
}
}
else {
StyleConstants.setBackground(style, Color.ORANGE);
try {
doc.insertString(doc.getLength(), message + "FILE OR FILES NOT FOUND\n", style);
}
catch (BadLocationException ex) {ex.printStackTrace();}
}
}
}
The doInBackground()
does the comparison, done()
formats the message according to the comparison's result and prints it. The problem is that the program does not wait until one pair is processed AND printed so the results are not printed in the order in which they are opened, which can be very confusing for the user: most of the files are small and go by really fast thus the comparison seems to be completed at some point but there are still bigger files being processed.
I read about the possibility of using a PropertyChangeListener
but I don't see how it differs from using the done()
method... I tried doing both comparing and printing in doInBackground()
but this messes up the formatting (which is to be expected - before the printing is done, the background color is changed). I also tried invoking Thread.sleep()
for an arbitrary amount of time inside the loop that calls the SwingWorker
which looked like this:
try (FileInputStream reportListExcelFile = new FileInputStream(new File(reportListPath))) {
Workbook workbook = new XSSFWorkbook(reportListExcelFile);
Sheet sheet = workbook.getSheetAt(0);
Iterator<Row> iter = sheet.iterator();
// skip first row that contains columns names
iter.next();
while (iter.hasNext()) {
try {Thread.sleep(1000);} catch (Exception ex) {ex.printStackTrace();}
Row r = iter.next();
String name = r.getCell(0).getStringCellValue();
String format = r.getCell(1).getStringCellValue();
Report currentReport = new Report(name, format);
new ProcessAndPrintTask(currentReport).execute();
}
}
Not only it seems to be an ugly crutch but also caused the GUI to hang until all the file pairs were compared.
Is there a solution?
Once I've done an OrderedResultsExecutors
that maintains the order of adding tasks with the order of notifying about results. All you have to do is to implement the notify method for your case eg. write some Listener
or something. Of course you could pass a collection of Report to the SwingWorker and process them in a for
loop, but in that case you'll lose multithreading, and all the tasks could take considerably more time to execute in such single-threaded manner. That's why it could be better to have a rally multithreaded version of such mechanism, like this:
Import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class OrderedResultsExecutors extends ThreadPoolExecutor {
public OrderedResultsExecutors(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
private ConcurrentHashMap<Long, Runnable> startedTasks = new ConcurrentHashMap<>();
private ConcurrentLinkedDeque<Runnable> finishedTasks = new ConcurrentLinkedDeque<>();
private AtomicLong toNotify = new AtomicLong(0);
private AtomicLong submitedCount = new AtomicLong(0);
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
startedTasks.put(submitedCount.getAndIncrement(), r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
finishedTasks.add(r);
finishedTask();
}
private void finishedTask() {
Runnable orderedResult;
long current;
while ((orderedResult = startedTasks.get(current = toNotify.get())) != null
&& finishedTasks.contains(orderedResult) && (orderedResult = startedTasks.remove(current)) != null) {
finishedTasks.remove(orderedResult);
notify(current, orderedResult);
toNotify.incrementAndGet();
}
}
private void notify(long order, Runnable result) {
try {
System.out.println("order: " + order + " result: " + ((Future)result).get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
public static ExecutorService newFixedThreadPool(int noOfThreads) {
int corePoolSize = noOfThreads;
int maximumPoolSize = noOfThreads;
return new OrderedResultsExecutors(corePoolSize, maximumPoolSize, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
}