Search code examples
javamultithreadinguser-interfaceswingworker

SwingWorker done method throws cancellationexception with get()


I faced an issue of creating stop/start jbuttons for my gui, and after a LOT of googling, i realized i needed multi-threading. Upon further reading i discovered the swingworker class, and i managed to get my GUI to respond to the STOP button.

now my problem is this

The doinbackground() method executes a piece of code that captures packets in an infinite while loop with the condition (!isCancelled), and once it is cancelled (The STOP button executes worker.cancel()) it returns an ArrayList of packets which theoretically, i should be able to obtain inside the done() method using get(). right? But when i try to do this i get a CancellationException and this is driving me nuts right now.

any help would be highly appreaciated! Thank you

edit: obj is an ArrayList declared outside of the class to store the return values.

here is my code executed by the START jbutton

private void jButton5ActionPerformed(java.awt.event.ActionEvent evt) {


    final ArrayList packet_list = new ArrayList();
    obj.clear();

    try {
        worker = new SwingWorker<ArrayList,Integer>(){//initialze swingworker class


            @Override
            protected void done(){

                try {  

                    obj = get();

                }
                catch (InterruptedException ex) {
                    Logger.getLogger(NewJFrame3.class.getName()).log(Level.SEVERE, null, ex);
                } catch (ExecutionException ex) {
                    Logger.getLogger(NewJFrame3.class.getName()).log(Level.SEVERE, null, ex);
                }

            }

            //opens up stuff required to capture the packets    
            NetworkInterface   [] devices = JpcapCaptor.getDeviceList();
            int index = (jComboBox5.getSelectedIndex()-1);
            JpcapCaptor captor =JpcapCaptor.openDevice(devices[4], 65535, false, 20); 

            @Override
            protected ArrayList doInBackground(){  

                while(!isCancelled()){

                    try {
                        Packet packets = captor.getPacket(); //captures packets

                        if (packets != null)  //filters out null packets
                        {

                            //System.out.println(packets);
                            packet_list.add(packets); //adds each packet to ArrayList
                        }
                        Thread.sleep(100);

                    } catch (InterruptedException ex) {
                        return packet_list;
                    }
                }
                return packet_list;

            }


        };

        worker.execute();
    } catch (IOException ex) {
        Logger.getLogger(NewJFrame3.class.getName()).log(Level.SEVERE, null, ex);
    }
}                                 


The stop button simply executes 

worker.cancel(); no errors there. and this is the swingworker declaration

 private SwingWorker<ArrayList,Integer> worker;

Solution

  • cancel doesn't just set the isCancelled flag for you to read at your leisure. That would be pretty much useless. It prevents the task from starting if it hasn't already and may actively interrupt the thread if it's already running. As such, getting a CancellationException is the natural consequence of cancelling a running task.

    To further the point, the Javadoc on isCancelled states:

    Returns true if this task was cancelled before it completed normally.

    Hence if this returns true, then your task cannot complete normally. You cannot cancel a task and expect it to continue as per normal.

    SwingWorker docs say "An abstract class to perform lengthy GUI-interaction tasks in a background thread". However, the definition of "lengthly" is different for GUI and for an application lifetime. A 100ms task is very long for a GUI, and is best done by a SwingWorker. A 10 minute task is too long for a SwingWorker simply because it has a limited thread pool, that you may exhaust. Judging by your problem description, you have exactly that - a potentially very long running task. As such, you should rather make a proper background thread than use a SwingWorker.

    In that thread, you would have either an AtomicBoolean or simply a volatile boolean flag that you can manually set from the EDT. The thread can then post an event to the EDT with the result.

    Code:

    class PacketCaptureWorker implements Runnable {
        private volatile boolean cancelled = false;
        public void cancel() {
            cancelled = true;
        }
        public void run() {
            while (!cancelled) {
                //do work
            }
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    //Use the result of your computation on the EDT
                }
            });
        }
    }
    
    new Thread(new PacketCaptureWorker()).start();