Search code examples
androidandroid-asynctaskcancellation

Why my AsyncTask is not being properly cancelled?


I made a filemanager that populates a listview through an AsyncTask populate(). If you have several hundred folders in a folder, it takes a couple of seconds to populate the listview adapter. However, you can click on a subfolder before the folder has been completely populated. In that case, the populate process is (should be!) cancelled, everything is cleaned, and a new AsyncTask with a new populate process begins.

My problem is that, if you don't wait, sometimes an element from the folder is carried to the subfolder, adding an entry in the subfolder that does not exist in it, but in the folder. In other words, the old AsyncTask persists writing the adapter, despite the fact that I test for it conclusion with a pop.isCancelled() test. In other words, two AsyncTasks are running at the same time, despite my check. I tried to check for pop.getStatus() != Status.FINISHED instead of pop.isCancelled(), but it always fails with a RUNNING status. Why i never get FINISHED??.

What am I doing wrong (code below)? How can I be sure that an AsyncTask REALLY finished? I understand that the dirs variable can be set after the isCancelled() test in the doInBackground(), but why the calling process is not detecting that the AsyncTask continues?

Ok, you could say that I should call the second populate() from the onPostExecute() of the first, but onPostExecute() is not executed when the AsyncTask is cancelled.

Here is my simple code. First, the calling process, then the relevant part of the AsyncTask:

public void startPopulating(File folder) {
    if (pop != null) {
        pop.cancel(true);
        while (pop != null && !pop.isCancelled()) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        pop = null;
    }
    pop = new populate();
    pop.run(folder);
}


class populate extends AsyncTask<Void, Boolean, Void> {
    public void run (File dir) {
        // Clean variables
        if (dirs != null) dirs.clear();
        if (files != null) files.clear();
        basedir = dir;
        execute();
    }
    @Override
    protected void onProgressUpdate(Boolean... loading) {
        if (!loading[0]) {
            loadAdapterData(dirs, !dirmodified);
            dirs.clear();
        }
    }
    @Override
    protected Void doInBackground(Void... params) {
        dirs = new ArrayList<String>();
        files = new ArrayList<String>();

        String[] allfiles = basedir.list();
        String thisfile;
        publishProgress(true);
        Arrays.sort(allfiles, String.CASE_INSENSITIVE_ORDER);

        for (Integer i = 0; i < allfiles.length; i++) {
            if (isCancelled()) break;
            thisfile = allfiles[i];
            if ((new File(basedir, thisfile)).isDirectory()) {
                dirs.add(thisfile); // THIS LINE IS BEING EXECUTED AFTER THE TEST
            } else {
                files.add(thisfile);
            }   
            if (i % 50 == 0 && dirs.size() > 0) publishProgress(false);
        }   

        if (!isCancelled() && dirs.size() > 0) publishProgress(false);

        return null;
    }   
}

Solution

  • It seems that this works: start the new populate() from the onCancelled() method of the AsyncTask using a global variable (changedir):

    public void startPopulating(File dir) {
        if (pop != null && pop.getStatus() != Status.FINISHED) {
            changedir = dir;
            pop.cancel(true);
        } else {
            changedir = null;
            pop = new populate();
            pop.run(dir);
        }
    }
    
    
    class populate extends AsyncTask<Void, Boolean, Void> {
        [....]                                                                                                                        
    
        @Override 
        protected void onCancelled(Void args) {
            super.onCancelled();
            if (changedir != null && changedir.isDirectory()) {
                pop = new populate();
                pop.run(changedir);
                changedir = null;
            }
        }
    }