Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AsyncTask keeps waiting?

A button in one of my activities calls an AsyncTask that updates the underlying Cursor for a ListView's SimpleCursorAdapter. Everytime I click the button, a new thread for the AsyncTask is added and the task completes (goes to 'wait' status). If I click the button 5 or more times, 5 AsyncTasks ends up sitting there with 'wait' status. Is this normal or do I have a memory leak somewhere?

The AsyncTask

private class updateAdapter extends AsyncTask<Void, Void, Void> {

    @Override
    protected Void doInBackground(Void... params) {
        // Open database connection
        if(_db == null || !_db.isOpen()) _db = new DatabaseWrapper(ActivityShowWOD.this).getWritableDatabase();
        Cursor WODcursor;

        // Check if a wod_id is set
        if(_wod_id == -1) {
            // Grab filters from preferences and at the same time build SQLselection string
            SharedPreferences prefs = getSharedPreferences("Preferences", 0);
            String[] filterNames = getResources().getStringArray(R.array.filters_values);
            boolean[] filterValues = new boolean[filterNames.length];
            String SQLselection = "";

            for (int i = 0; i < filterNames.length; i++) {
                filterValues[i] = prefs.getBoolean(filterNames[i], false);

                // Build SQL query
                if(filterValues[i] == true) {
                    SQLselection += filterNames[i] + " = 1 OR " +  filterNames[i] + " = 0";
                } else {
                    SQLselection += filterNames[i] + " = 0";
                }

                // Add an "AND" if there are more filters 
                if(i < filterNames.length - 1) SQLselection += " AND ";
            }

            // Get all WODs matching filter preferences 
            WODcursor = _db.query(DatabaseConstants.TBL_WORKOUTS, 
                                      new String[] { DatabaseConstants.WORKOUTS_ID, DatabaseConstants.WORKOUTS_NAME,
                                                     DatabaseConstants.WORKOUTS_NOTES, DatabaseConstants.WORKOUTS_CFID }, 
                                      SQLselection, null, null, null, null);

            // Move the Cursor to a random position
            Random rand = new Random();
            WODcursor.moveToPosition(rand.nextInt(WODcursor.getCount()));

            // Store wod_id
            _wod_id = WODcursor.getInt(WODcursor.getColumnIndex(DatabaseConstants.WORKOUTS_ID));
        } else {
            // Get the cursor from the wod_id
            WODcursor = _db.query(DatabaseConstants.TBL_WORKOUTS, 
                                 new String[] { DatabaseConstants.WORKOUTS_ID, DatabaseConstants.WORKOUTS_NAME,
                                                DatabaseConstants.WORKOUTS_NOTES, DatabaseConstants.WORKOUTS_CFID }, 
                                 DatabaseConstants.WORKOUTS_ID + " = " + _wod_id, null, null, null, null);

            WODcursor.moveToFirst();
        }

        // Store WOD information into class instance variables and close cursor
        _wod_cfid = WODcursor.getInt(WODcursor.getColumnIndex(DatabaseConstants.WORKOUTS_CFID));
        _wod_name = WODcursor.getString(WODcursor.getColumnIndex(DatabaseConstants.WORKOUTS_NAME));
        _wod_notes = WODcursor.getString(WODcursor.getColumnIndex(DatabaseConstants.WORKOUTS_NOTES));
        WODcursor.close();

        // Return all exercises pertaining to this WOD
        _excCursor = _db.query(DatabaseConstants.TBL_EXERCISES, 
                              new String[] { DatabaseConstants.EXERCISES_ID, DatabaseConstants.EXERCISES_EXERCISE,
                                             DatabaseConstants.EXERCISES_REPS, DatabaseConstants.EXERCISES_NOTES }, 
                              DatabaseConstants.EXERCISES_WOD_ID + " = " + _wod_id, null, null, null, 
                              DatabaseConstants.EXERCISES_ID + " ASC");
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        _adapter.changeCursor(_excCursor);
        _adapter.notifyDataSetChanged();
        _WODlist.setOnItemClickListener(new WODlistClickListener());
    }

}

And the code in my onCreate that calls the task (when the activity is first loaded):

upAdapter = new updateAdapter().execute();

And in the button onClickListener:

            // Reset wod_id
            _wod_id = -1;

            // Update the underlying SimpleCursorAdapter
            upAdapter = new updateAdapter().execute();

Stacktrace of one of the AsyncTask (it's the same for all of them):

Object.wait(long, int) line: not available [native method]  
Thread.parkFor(long) line: 1535 
LangAccessImpl.parkFor(long) line: 48   
Unsafe.park(boolean, long) line: 317    
LockSupport.park() line: 131    
AbstractQueuedSynchronizer$ConditionObject.await() line: 1996   
LinkedBlockingQueue.take() line: 359    
ThreadPoolExecutor.getTask() line: 1001 
ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1061  
ThreadPoolExecutor$Worker.run() line: 561   
Thread.run() line: 1096 
like image 837
soren.qvist Avatar asked Jan 18 '23 03:01

soren.qvist


2 Answers

AsyncTask under the hood uses a ThreadPoolExecutor. Those threads might not go away for a bit because it'd be a waste to keep creating and tearing down those threads too often. After a while if you create more AsyncTasks you'll find that it'll stop creating new threads and it'll re-use the old ones.

Update to address some details:

You would think that if there are free threads in the pool, it wouldn't create new ones, but this isn't exactly true. The idea is that there's a certain number of threads that are useful to have around to keep processing asynchronous tasks. This is called the core pool size. In Android's AsyncTask case, they seem to have set it to 5. If you look at the documentation for ThreadPoolExecutor it says:

When a new task is submitted in method execute(Runnable), and fewer than corePoolSize threads are running, a new thread is created to handle the request, even if other worker threads are idle.

There's also a maximum fittingly called the maximum pool size.

like image 176
kabuko Avatar answered Jan 21 '23 17:01

kabuko


What @kabuko says is true but I also think that it is a good practice to cancel the task before starting a new one. You could have some weird behaviors in case one of the old task keep going. More over, in your case you don't want to query your db more than once, it would be useless. You could wrap the call to the async task in a method like this:

AsyncDataLoading loaderTask = null;

private void runTask(){
    if (loaderTask!=null && loaderTask.getStatus().compareTo(Status.FINISHED)!=0) {
        loaderTask.cancel(true);
    }
    loaderTask = new AsyncDataLoading();
    loaderTask.execute();
} 

It also a good practice to disable the button and re-enable it when the async task is done.

Anyway this solution could not be a good fit for your architecture, I don't know enough about your code. Hope it will help anyway.

like image 34
gwvatieri Avatar answered Jan 21 '23 17:01

gwvatieri