programing tip

Android AsyncTask 스레드 제한?

itbloger 2020. 8. 28. 07:08
반응형

Android AsyncTask 스레드 제한?


사용자가 시스템에 로그인 할 때마다 일부 정보를 업데이트해야하는 응용 프로그램을 개발 중이며 전화에서도 데이터베이스를 사용합니다. 모든 작업 (업데이트, db에서 데이터 검색 등)에 대해 비동기 작업을 사용합니다. 지금까지는 왜 사용하지 말아야하는지 알지 못했지만 최근에 일부 작업을 수행하면 일부 비동기 작업이 사전 실행에서 중지되고 doInBackground로 이동하지 않는 것을 경험했습니다. 그렇게 놔두기에는 너무 이상했기 때문에 문제를 확인하기 위해 또 다른 간단한 응용 프로그램을 개발했습니다. 이상하게도 총 비동기 작업 수가 5 개에 도달하면 동일한 동작이 발생하고 6 번째 작업은 사전 실행에서 중지됩니다.

Android에는 활동 / 앱에 대한 asyncTasks 제한이 있습니까? 아니면 버그 일 뿐이며보고해야합니까? 누구든지 동일한 문제를 경험하고 아마도 해결 방법을 찾았습니까?

다음은 코드입니다.

백그라운드에서 작동하도록 5 개의 스레드를 생성하기 만하면됩니다.

private class LongAsync extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");
        isRunning = true;
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        while (isRunning)
        {

        }
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        Log.d("TestBug","onPostExecute");
    }
}

그리고이 스레드를 만듭니다. preExecute로 들어가고 중단됩니다 (doInBackground로 이동하지 않음).

private class TestBug extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");

        waiting = new ProgressDialog(TestActivity.this);
        waiting.setMessage("Loading data");
        waiting.setIndeterminate(true);
        waiting.setCancelable(true);
        waiting.show();
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        waiting.cancel();
        Log.d("TestBug","onPostExecute");
    }
}

모든 AsyncTask는 공유 (정적) ThreadPoolExecutorLinkedBlockingQueue에 의해 내부적으로 제어됩니다 . executeAsyncTask 를 호출하면 ThreadPoolExecutor은 (는) 나중에 준비가되면 실행합니다.

The 'when am I ready?' behavior of a ThreadPoolExecutor is controlled by two parameters, the core pool size and the maximum pool size. If there are less than core pool size threads currently active and a new job comes in, the executor will create a new thread and execute it immediately. If there are at least core pool size threads running, it will try to queue the job and wait until there is an idle thread available (i.e. until another job is completed). If it is not possible to queue the job (the queue can have a max capacity), it will create a new thread (up-to maximum pool size threads) for the jobs to run in. Non-core idle threads can eventually be decommissioned according to a keep-alive timeout parameter.

Before Android 1.6, the core pool size was 1 and the maximum pool size was 10. Since Android 1.6, the core pool size is 5, and the maximum pool size is 128. The size of the queue is 10 in both cases. The keep-alive timeout was 10 seconds before 2.3, and 1 second since then.

With all of this in mind, it now becomes clear why the AsyncTask will only appear to execute 5/6 of your tasks. The 6th task is being queued up until one of the other tasks complete. This is a very good reason why you should not use AsyncTasks for long-running operations - it will prevent other AsyncTasks from ever running.

For completeness, if you repeated your exercise with more than 6 tasks (e.g. 30), you will see that more than 6 will enter doInBackground as the queue will become full and the executor is pushed to create more worker threads. If you kept with the long-running task, you should see that 20/30 become active, with 10 still in the queue.


@antonyt has the right answer but in case you are seeking for a simple solution then you may check out Needle.

With it you can define a custom thread pool size and, unlike AsyncTask, it works on all Android versions the same. With it you can say things like:

Needle.onBackgroundThread().withThreadPoolSize(3).execute(new UiRelatedTask<Integer>() {
   @Override
   protected Integer doWork() {
       int result = 1+2;
       return result;
   }

   @Override
   protected void thenDoUiRelatedWork(Integer result) {
       mSomeTextView.setText("result: " + result);
   }
});

or things like

Needle.onMainThread().execute(new Runnable() {
   @Override
   public void run() {
       // e.g. change one of the views
   }
}); 

It can do even lot more. Check it out on GitHub.


Update: Since API 19 the core thread pool size was changed to reflect the number of CPUs on the device, with a minimum of 2 and maximum of 4 at start, while growing to a max of CPU*2 +1 - Reference

// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

Also note that while the default executor of AsyncTask is serial (executes one task at a time and in the order in which they arrive), with the method

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params)

you can provide an Executor to run your tasks. You may provide the THREAD_POOL_EXECUTOR the under the hood executor but with no serialization of tasks, or you can even create your own Executor and provide it here. However, carefully note the warning in the Javadocs.

Warning: Allowing multiple tasks to run in parallel from a thread pool is generally not what one wants, because the order of their operation is not defined. For example, if these tasks are used to modify any state in common (such as writing a file due to a button click), there are no guarantees on the order of the modifications. Without careful work it is possible in rare cases for the newer version of the data to be over-written by an older one, leading to obscure data loss and stability issues. Such changes are best executed in serial; to guarantee such work is serialized regardless of platform version you can use this function with SERIAL_EXECUTOR.

One more thing to note is that both the framework provided Executors THREAD_POOL_EXECUTOR and its serial version SERIAL_EXECUTOR (which is default for AsyncTask) are static (class level constructs) and hence shared across all instances of AsyncTask(s) across your app process.

참고URL : https://stackoverflow.com/questions/9654148/android-asynctask-threads-limits

반응형