web-dev-qa-db-ja.com

Android AsyncTaskスレッドの制限?

ユーザーがシステムにログインするたびに情報を更新する必要があるアプリケーションを開発していますが、電話のデータベースも使用しています。これらのすべての操作(更新、dbからのデータの取得など)には、非同期タスクを使用します。今までのところ、それらを使用しない理由はわかりませんでしたが、最近、いくつかの操作を行うと、非同期タスクの一部が実行前に停止してdoInBackgroundにジャンプしないことがわかりました。それはあまりにも奇妙なので、そのままにしておくことはできないので、間違っているかどうかを確認するためだけに別の簡単なアプリケーションを開発しました。奇妙なことに、合計非同期タスクのカウントが5に達すると、同じ動作になります。6番目のタスクは実行前に停止します。

Android Activity/Appに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");
    }
}
94

すべてのAsyncTasksは、共有(静的) ThreadPoolExecutor および LinkedBlockingQueue によって内部的に制御されます。 AsyncTaskでexecuteを呼び出すと、ThreadPoolExecutorは、将来の準備ができたときに実行します。

「いつ準備ができましたか?」 ThreadPoolExecutorの動作は、2つのパラメーターコアプールサイズおよび最大プールによって制御されますサイズ。現在アクティブなコアプールサイズ未満のスレッドがあり、新しいジョブが入った場合、エグゼキューターは新しいスレッドを作成し、すぐに実行します。少なくともコアプールサイズのスレッドが実行されている場合、ジョブをキューに入れて、アイドルスレッドが利用可能になるまで(つまり、別のジョブが完了するまで)待機します。ジョブをキューに入れることができない場合(キューに最大容量を持たせることができます)、ジョブを実行するための新しいスレッド(最大プールサイズスレッドまで)が作成されます。非コアアイドルスレッドは最終的に廃止されます。キープアライブタイムアウトパラメータに従って。

Android 1.6、コアプールサイズは1、最大プールサイズは10でした。Android 1.6、コアプールサイズは5、最大キューのサイズはどちらの場合も10であり、キープアライブタイムアウトは2.3の10秒前から1秒でした。

これらすべてを念頭に置いて、AsyncTaskがタスクの5/6を実行するように見える理由が明らかになりました。 6番目のタスクは、他のタスクのいずれかが完了するまでキューに入れられます。これは、長時間実行される操作にAsyncTasksを使用すべきではない非常に良い理由です。他のAsyncTasksが実行されないようにします。

完全を期すために、6つ以上のタスク(30など)で運動を繰り返した場合、キューがいっぱいになり、より多くのワーカースレッドを作成するためにエグゼキューターがプッシュされるときに、6つ以上がdoInBackgroundに入ることがわかります。長時間実行されるタスクを続けると、20/30がアクティブになり、10がまだキューに残っていることがわかります。

206
antonyt

@antonytには正しい答えがありますが、シンプルなソリューションを探している場合は、Needleをチェックアウトできます。

これを使用すると、カスタムスレッドプールサイズを定義でき、AsyncTaskとは異なり、allAndroidバージョンと同じように動作します。それを使用すると、次のようなことを言うことができます。

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);
   }
});

またはのようなもの

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

さらに多くのことができます。 GitHub で確認してください。

9
Zsolt Safrany

Update:API 19以降、コアスレッドプールサイズは、デバイス上のCPUの数を反映するように変更され、最小で2、最大で4最大CPU * 2 +1に成長しながら開始- 参照

// 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;

また、AsyncTaskのデフォルトのエグゼキューターはシリアルですが(一度に1つのタスクを到着順に実行します)、 method

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

タスクを実行するエグゼキューターを提供できます。 THREAD_POOL_EXECUTORを内部のエクゼキューターに提供できますが、タスクをシリアル化することはできません。または、独自のエグゼキューターを作成してここで提供することもできます。ただし、Javadocsの警告に注意してください。

警告:スレッドプールから複数のタスクを並列に実行できるようにすることは、通常、操作の順序が定義されていないため、望むものではありません。たとえば、これらのタスクを使用して共通の状態を変更する場合(ボタンのクリックによるファイルの書き込みなど)、変更の順序は保証されません。注意深い作業を行わないと、まれに、新しいバージョンのデータが古いバージョンで上書きされ、データの損失と安定性の問題があいまいになります。このような変更は、逐次実行するのが最適です。プラットフォームのバージョンに関係なく、このような作業が確実にシリアル化されるようにするには、この関数をSERIAL_EXECUTORで使用できます。

もう1つ注意すべき点は、フレームワークが提供するエグゼキューターTHREAD_POOL_EXECUTORとそのシリアルバージョンSERIAL_EXECUTOR(AsyncTaskのデフォルト)の両方が静的(クラスレベルの構成)であり、アプリプロセス全体でAsyncTaskのすべてのインスタンスで共有されることです。

5
rnk