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
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With