Android AsyncTaskスレッドの制限?


95

ユーザーがシステムにログインするたびに情報を更新する必要があるアプリケーションを開発しています。電話のデータベースも使用しています。これらすべての操作(更新、dbからのデータの取得など)には、非同期タスクを使用します。これまで、なぜそれらを使用すべきでないのかわかりませんでしたが、最近、いくつかの操作を実行すると、非同期タスクの一部が実行前に停止し、doInBackgroundにジャンプしないことがわかりました。それはあまりにも奇妙で、そのままにしておくことはできなかったので、何が問題かを確認するだけの簡単なアプリケーションをもう1つ開発しました。そして奇妙なことに、非同期タスクの総数が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");
    }
}

回答:


207

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

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

Android 1.6より前は、コアプールサイズは1で、最大プールサイズは10でした。Android1.6以降、コアプールサイズは5、最大プールサイズは128です。キューのサイズはどちらの場合も10です。キープアライブタイムアウトは2.3以前は10秒、それ以降は1秒でした。

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

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


2
「これは、長時間実行オペレーションにAsyncTasksを使用すべきでない非常に良い理由です」このシナリオでの推奨事項は何ですか?新しいスレッドを手動で生成するか、独自のエグゼキューターサービスを作成しますか?
user123321 2012

2
エグゼキュータは基本的にスレッドの上にある抽象であり、それらを管理するために複雑なコードを記述する必要性を軽減します。これにより、タスクの実行方法からタスクが切り離されます。コードがエグゼキューターのみに依存している場合、使用されるスレッドの数を透過的に変更するのは簡単です。自分でスレッドを作成する十分な理由は考えられません。単純なタスクでも、エグゼキューターは、少なくないにしても同じです。
antonyt

37
:アンドロイド3.0+から、同時AsyncTasksのデフォルト数は1詳細情報に削減されたことをしてくださいノートdeveloper.android.com/reference/android/os/...
キーラン

すばらしい回答に感謝します。最後に、コードが散発的かつ不可解に失敗する理由について説明します。
クリスナイト

@antonyt、もう1つの疑問、キャンセルされたAsyncTasks、それはAsyncTasksの数にカウントされますか?つまり、core pool sizeまたはでカウントされmaximum pool sizeますか?
nmxprime 2014

9

@antonytには正しい答えがありますが、簡単な解決策を探している場合は、Needleをチェックしてください。

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

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で確認してください。


最後のコミットは5年前
でした

5

更新: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つのタスクを、タスクが到着する順序で実行します)、

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.