Reputation: 41
Problem: I want the main application window to close after all the worker threads are stopped. That's easy. But I also want to be sure that messages sent by SwingWorker will be processed before messages sent by closing window. I have this (not so) small example source file here.
/*****************************************************************************
TestAsyncEvents.java
This example shows that messages from SwingWorker threads get processed
*AFTER* the WINDOW_CLOSED event even if they had been generated
*BEFORE* them.
The question is: HOW DO I MAKE IT RIGHT?
I know the answer, but it doesn't satisfy me. ;)
*****************************************************************************/
import java.util.List;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
//////////////////////////////////////////////////////////////////////////////
//
public class TestAsyncEvents extends JFrame
{
/*
* This class is for closing main window
*/
private class Closer extends WindowAdapter
{
@Override public void windowClosing( WindowEvent ev ) { handleWindowClosing(); }
@Override public void windowClosed( WindowEvent ev ) { handleWindowClosed(); }
}
/*
* This class represents worker that gets asked to stop
* and then reports when it actually stops
*/
private class Ticker extends SwingWorker<Boolean,String>
{
private boolean stop_flag = false;
private int counter = 0;
private boolean result;
Ticker() { super(); execute(); }
@Override protected Boolean doInBackground()
{
// body, executed in worker thread
try {
while( !stop_flag )
{
Thread.sleep(2000);
publish(String.format("Tick #%d",++counter));
}
return result=true;
} catch( Exception ex ) {
return result=false;
}
}
@Override protected void process( List<String> chunks )
{
// reports progress, executed in gui thread
for( String chunk: chunks )
System.out.println(String.format("Chunk processed: %s",chunk));
}
@Override protected void done()
{
// reports result, executed in gui thread
System.out.println(String.format("Thread is %s.",isCancelled()?"cancelled":"stopped normally"));
System.out.println(String.format("Result is %s.",Boolean.toString(result)));
//postClosing(); // IT IS THE SOLUTION FOR MY PROBLEM! BUT... IT ISN'T GOOD ONE!
}
public void askToStop() { stop_flag = true; }
}
/****************************************************************************/
/* FIELDS */
/****************************************************************************/
private static TestAsyncEvents self;
private Ticker worker_object = null;
/****************************************************************************/
/* CONSTRUCTOR */
/****************************************************************************/
TestAsyncEvents()
{
super("Testing Async Events");
addWindowListener(new Closer());
setMinimumSize(new Dimension(512,384));
setVisible(true);
worker_object = new Ticker();
}
/****************************************************************************/
/* INNER METHODS */
/****************************************************************************/
/*
* Waiting for worker to finish
*/
private void doStopping()
{
worker_object.askToStop();
while( !worker_object.isDone() );
}
private boolean stopInSeparateThread()
{
try {
Thread closer = new Thread(new Runnable(){public void run(){doStopping();}});
closer.start();
closer.join();
return true;
} catch( Exception ex ) {
return false;
}
}
private boolean stopHere()
{
doStopping();
return true;
}
private boolean stopWorker()
{
//return stopInSeparateThread();
return stopHere();
}
private boolean canClose()
{
return worker_object.isDone();
}
/*
* Posting WM_CLOSE events
*/
private void doPostCloseEvent()
{
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new WindowEvent(this,WindowEvent.WINDOW_CLOSING));
}
private void postInSeparateThread()
{
SwingUtilities.invokeLater(new Runnable(){public void run(){doPostCloseEvent();}});
}
private void postHere()
{
doPostCloseEvent();
}
private void postClosing()
{
//postInSeparateThread();
postHere();
}
/*
* Methods for Closer class
*/
private void handleWindowClosing()
{
System.out.println("Closing main window...");
if( canClose() )
{
System.out.println("Can close! Disposing...");
dispose();
} else {
System.out.println("Can't close! Now we'll allow it by stopping worker thread...");
boolean res = stopWorker();
System.out.println(String.format("Stopping worker thread went %s.",res?"okay":"wrong"));
postClosing(); // HERE I SIGNAL THE MAIN WINDOW TO CLOSE
}
}
private void handleWindowClosed()
{
System.out.println("Main window is closed!");
}
/****************************************************************************/
/* ENTRY POINT */
/****************************************************************************/
public static void main( final String[] args )
{
SwingUtilities.invokeLater(new Runnable(){public void run(){self=new TestAsyncEvents();}});
System.out.println("All systems are go!");
}
}
//////////////////////////////////////////////////////////////////////////////
Its output is here:
F:\C\Java\Test-Frame-Events>java TestAsyncEvents
All systems are go!
Chunk processed: Tick #1
Closing main window...
Can't close! Now we'll allow it by stopping worker thread...
Stopping worker thread went okay.
Closing main window...
Can close! Disposing...
Main window is closed!
Chunk processed: Tick #2
Thread is stopped normally.
Result is true.
What I want is here:
F:\C\Java\Test-Frame-Events>java TestAsyncEvents
All systems are go!
Chunk processed: Tick #1
Closing main window...
Can't close! Now we'll allow it by stopping worker thread...
Stopping worker thread went okay.
Chunk processed: Tick #2
Thread is stopped normally.
Result is true.
Closing main window...
Can close! Disposing...
Main window is closed!
It occurs to me that events from SwingWorker are handled in a completely different event queue, not the one that handles window messages and such. I intentionally wait for worker thread to stop and post all its messages BEFORE I post the WINDOW_CLOSING event. But it doesn't help - messages from SwingWorker still get handled AFTER the WINDOW_CLOSING and WINDOW_CLOSED events. This leads to numerous tiny inconveniences. Particularly, as I close all the logging in WINDOW_CLOSED handler, hoping that those will be the last operators executed in my program, all the messages from worker threads are lost in time and space.
I know the solution for my problem. I have to uncomment line #68 and comment line #161. But it means that if I have more than one SwingWorker thread, I should spawn another one that will just look after all the workers, and signal the main window to quit when all of them are stopped. And that's just not neat, IMHO. So, Java gurus, how do you suggest me to fix this?
Upvotes: 2
Views: 1195
Reputation: 285405
One possible solution: consider using a PropertyChangeListener:
private void handleWindowClosing() {
System.out.println("Closing main window...");
if (canClose()) {
System.out.println("Can close! Disposing...");
dispose();
} else {
System.out
.println("Can't close! Now we'll allow it by stopping worker thread...");
worker_object.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (SwingWorker.StateValue.DONE.equals(pcEvt.getNewValue())) {
postClosing();
}
}
});
boolean res = stopWorker();
System.out.println(String.format("Stopping worker thread went %s.",
res ? "okay" : "wrong")); // !!
// postClosing(); // HERE I SIGNAL THE MAIN WINDOW TO CLOSE
}
}
If you have multiple workers, you could possibly use CountDownLatch or a CyclicBarrier
Upvotes: 2