简体   繁体   中英

My AsyncTask class has other threads in it. How can i wait for it and all of its threads to finish before executing a following method?

The AsyncTask class starts many other threads because i am using a third party library. I am not sure how many threads the library opens but i want to wait for them all to finish before populating my listview with the data they fetch.

At the moment I am sleeping 10000 milliseconds but this is not practical because I don't know how big the list it.

What is the correct solution for this problem?

task = new mTask();
        task.execute(appsList);
        new Thread(new Runnable() {
            public void run() {
                populateList();
            }
        }).start();

    }

    private class mTask extends AsyncTask<List<ApplicationInfo>, Void, Void> {
        ProgressDialog progress;

        @Override
        protected void onPreExecute() {
            progress = new ProgressDialog(MainActivity.this);
            progress.setIndeterminate(true);
            progress.show();
            super.onPreExecute();
        }

        @SuppressWarnings("deprecation")
        @Override
        protected Void doInBackground(List<ApplicationInfo>... params) {
            appDataManager = new AppDataManager(MainActivity.this,
                    mySQLiteAdapter, MainActivity.this);
            appDataManager.work(params[0]);
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            mySQLiteAdapter.close();
            progress.dismiss();
            super.onPostExecute(result);
        }
    }

    @SuppressWarnings("deprecation")
    public void populateList() {
        try {
            Thread.sleep(10000)
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        runOnUiThread(new Runnable() {

            public void run() {

                Cursor cursor;
                mySQLiteAdapter.openToRead();
                cursor = mySQLiteAdapter.queueAll();

                ArrayList<String> appsList = new ArrayList<String>();
                if (cursor.moveToFirst()) {
                    do {
                        appsList.add(cursor.getString(1));
                    } while (cursor.moveToNext());
                }
                cursor.moveToFirst();

                ArrayAdapter<String> adp = new ArrayAdapter<String>(
                        MainActivity.this, android.R.layout.simple_list_item_1,
                        appsList);
                listContent.setAdapter(adp);
                cursor.close();
                mySQLiteAdapter.close();

                Log.i("finished", "finished");
         }
         });

    }

AppDataManager

public void work(List<ApplicationInfo> appsList) {
    List<ApplicationInfo> appList = appsList;
    mySQLiteAdapter.openToWrite();
    mySQLiteAdapter.deleteAll();
    mySQLiteAdapter.close();
    for (int i = 0; i < 5; i++) {
        String name = appList.get(i).name;
        String pack = appList.get(i).packageName;
        // TODO AsyncTask
        getHtml(pack, name);
    }

}

public void getHtml(final String pack, final String name) {
    String url = MARKET_URL + pack;
            //AndroidQuery library. fetch html
    aq.ajax(url, String.class, 1000, new AjaxCallback<String>() {
        @Override
        public void callback(String url, String htm, AjaxStatus status) {
            Log.i("status", status.getMessage());
            parseHtml(htm, pack, name);

        }
    });
}

First, I would move the call to populateList into the onPostExecute method of your AsyncTask . I would also rewrite populateList to remove the sleep and assume that it is running on the UI thread (eliminate the runOnUiThread call and move the body of the run method directly into populateList ).

Now to prevent the AsyncTask from completing doInBackground until the AppDataManager finishes its work. Start by defining a completion flag and a lock object on which you can synchronize:

private class mTask extends AsyncTask<List<ApplicationInfo>, Void, Void> {
    boolean complete;
    static Object LOCK = new Object();
    . . .
}

Then modify the AppDataManager class to provide a call-back to another object when the work is done. Define a field callback and update your api and methods:

public void work(List<ApplicationInfo> appsList, Runnable callback) {
    this.callback = callback; // define a field named "callback"
    List<ApplicationInfo> appList = appsList;
    mySQLiteAdapter.openToWrite();
    mySQLiteAdapter.deleteAll();
    mySQLiteAdapter.close();
    for (int i = 0; i < 5; i++) {
        String name = appList.get(i).name;
        String pack = appList.get(i).packageName;
        // TODO AsyncTask
        getHtml(pack, name);
    }

}

public void getHtml(final String pack, final String name) {
    String url = MARKET_URL + pack;
            //AndroidQuery library. fetch html
    aq.ajax(url, String.class, 1000, new AjaxCallback<String>() {
        @Override
        public void callback(String url, String htm, AjaxStatus status) {
            Log.i("status", status.getMessage());
            parseHtml(htm, pack, name);
            if (callback != null) {
                callback.run();
            }
        }
    });
}

Now modify your doInBackground method to wait for the flag to be set:

protected Void doInBackground(List<ApplicationInfo>... params) {
    appDataManager = new AppDataManager(MainActivity.this,
            mySQLiteAdapter, MainActivity.this);
    appDataManager.work(params[0], new Runnable() {
        public void run() {
            synchronized (LOCK) {
                complete = true;
                LOCK.notifyAll();
            }
        }
    });
    // wait for appDataManager.work() to finish...
    synchronized (LOCK) {
        while (!complete) {
            LOCK.wait();
        }
    }
    return null;
}

This should do the job. However, you probably should elaborate on this to deal with errors of various sorts (eg, provide an error notification mechanism for AppDataManager ).

UPDATE I finally noticed that you are doing five network transactions in AppDataManager . So instead of running the callback immediately inside the callback method for ajax() , decrement a counter and only call back once the counter reaches 0. Initialize the counter to 5 in work() before you enter the loop that calls getHtml() . Since the counter will be modified by separate threads, access to it needs to be synchronized. (Alternatively, you can use an AtomicInteger for the counter.

CyclicBarrier seems like the solution if you're waiting for threads to reach a common point

Also, instead of creating a thread to call populatelist() which is itselt creating the thread runOnUiThread , I'd look at publishProgress()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM