AsyncTask一直在等待?

4
在我的一个活动中,有一个按钮调用了一个AsyncTask来更新ListView的SimpleCursorAdapter的底层Cursor。每次我点击按钮,都会添加一个新线程到AsyncTask,并且任务完成后(进入“等待”状态)。如果我点击按钮5次或更多次,那么5个AsyncTasks就会以“等待”状态存在。这是正常现象还是我在某个地方出现了内存泄漏?
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());
    }

}

我的onCreate中调用任务的代码(当活动首次加载时):

upAdapter = new updateAdapter().execute();

在按钮的 onClickListener 中:

            // Reset wod_id
            _wod_id = -1;

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

其中一个AsyncTask的堆栈跟踪(对于所有的AsyncTask都是相同的):

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上调用execute()还是只是创建对象? - devunwired
你能否发布那些正在睡眠的AsynTask线程的完整堆栈跟踪信息?从我的经验来看,它们中应该没有太多处于停滞状态的。 - inazaruk
另外,您正在测试哪个Android版本? - inazaruk
@inazaruk Android 2.2。我如何为AsyncTask线程获取堆栈跟踪? - soren.qvist
1
@@soren.qvist,从堆栈跟踪来看,@kabuko是正确的。 - inazaruk
显示剩余3条评论
2个回答

8
在底层,AsyncTask使用了一个ThreadPoolExecutor。这些线程可能不会立即消失,因为经常创建和销毁这些线程是一种浪费。过一段时间,如果您创建更多的AsyncTasks,您会发现它不会再创建新线程,而是重复使用旧线程。
更新以解决一些细节:
您可能认为,如果池中有空闲线程,就不会创建新的线程,但这并不完全正确。想法是保持一定数量的线程以处理异步任务。这称为核心池大小。在Android的AsyncTask案例中,它们似乎已将其设置为5。如果您查看ThreadPoolExecutor的文档,它说:
当在execute(Runnable)方法中提交新任务时,并且运行的核心池大小线程少于corePoolSize,则创建一个新线程来处理请求,即使其他工作线程处于空闲状态。
还有一个最大值,恰当地称为最大池大小。

没错,但如果池中有一些空闲线程,为什么要创建新的呢?实际上不应该这样做。 - inazaruk
添加了一些细节以回答你的问题。 - kabuko
看起来你是正确的。AsyncTask 的核心池大小确实为5(请参见http://goo.gl/i2QMa)。 - inazaruk
谢谢大家。我总能依赖于StackOverflow :) - soren.qvist

1

@kabuko所说的是正确的,但我认为在开始新任务之前取消任务也是一个好习惯。如果旧任务中的某个任务继续运行,可能会出现一些奇怪的行为。此外,在您的情况下,您不希望查询数据库超过一次,这将是无用的。您可以像这样将对异步任务的调用包装在一个方法中:

AsyncDataLoading loaderTask = null;

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

在异步任务完成时禁用按钮并重新启用它也是一个好的实践。

无论如何,这个解决方案可能不适合您的架构,我不太了解您的代码。希望它能帮到您。


谢谢你的建议,但我不确定我理解你发布的代码(AsyncTask对我来说是全新的),AsyncDataLoading似乎并不是来自于Android库? - soren.qvist
1
没问题。AsyncDataLoading只是扩展AsyncTask的类名,在你的情况下是“updateAdapter”... - gwvatieri
糟糕..我再次仔细阅读了你的代码,现在它有意义了。谢谢。 - soren.qvist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接