AsyncTaskを数回実行する


127

私のアクティビティでは、AsyncTaskから拡張したクラスと、そのAsyncTaskのインスタンスであるパラメーターを使用しています。私が電話mInstanceOfAT.execute("")するとき、すべては大丈夫です。しかし、AsyncTaskを再度呼び出す更新ボタンを押すと、アプリがクラッシュします(ネットワークジョブが機能しなかった場合)。原因はそれから言う例外が表示されます

タスクを実行できません:タスクはすでに実行されています(タスクは一度だけ実行できます)

Asyctaskのインスタンスに対してcancel(true)を呼び出してみましたが、どちらも機能しません。これまでの唯一の解決策は、Asyntaskの新しいインスタンスを作成することです。それは正しい方法ですか?

ありがとう。

回答:


217

AsyncTask インスタンスは1回しか使用できません。

代わりに、次のようにタスクを呼び出します new MyAsyncTask().execute("");

AsyncTask APIドキュメントから:

スレッドのルール

このクラスが正しく機能するために従う必要のあるいくつかのスレッド化ルールがあります。

  • タスクインスタンスは、UIスレッドで作成する必要があります。
  • execute(Params ...)は、UIスレッドで呼び出す必要があります。
  • onPreExecute()、onPostExecute(Result)、doInBackground(Params ...)、onProgressUpdate(Progress ...)を手動で呼び出さないでください。
  • タスクは1回だけ実行できます(2回目の実行を試みると例外がスローされます)。

2
私が言ったことは、それが唯一の可能性ですか?原因新しいオブジェクトを作成するのではなく、メモリを保存したいのですが。
Dayerman、2011年


@StevePrentice:新しいtask()。execute(param)を使用してx秒ごとにタスクのインスタンスを作成し、サーバーにデータを送信する場合、実行が完了するとガベージコレクターはどのようにメモリを解放できますか?
Ant4res 2013年

3
@ Ant4res、非同期タスクインスタンスを参照していない限り、GCはメモリを解放します。ただし、継続的なバックグラウンドタスクがある場合は、doInBackground内のループで実行することを検討し、publishProgressを呼び出して進行状況を更新できます。または、別のアプローチは、タスクをバックグラウンドスレッドに入れることです。ここにはさまざまなアプローチがたくさんありますが、詳細がないと、どちらかを推奨することはできません。
Steve Prentice 2013年

28

ASyncTaskのファイアアンドフォーゲットインスタンスの理由は、スティーブプレンティスの回答でかなり詳しく説明されています。ただし、ASyncTaskを実行する回数は制限されていますが、スレッドの実行中に自由に実行できます。 。

実行可能コードをdoInBackground()内のループ内に配置し、同時ロックを使用して各実行をトリガーします。publishProgress()/ onProgressUpdate()を使用して結果を取得できます。

例:

class GetDataFromServerTask extends AsyncTask<Input, Result, Void> {

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition tryAgain = lock.newCondition();
    private volatile boolean finished = false;

    @Override
    protected Void doInBackground(Input... params) {

        lock.lockInterruptibly();

        do { 
            // This is the bulk of our task, request the data, and put in "result"
            Result result = ....

            // Return it to the activity thread using publishProgress()
            publishProgress(result);

            // At the end, we acquire a lock that will delay
            // the next execution until runAgain() is called..
            tryAgain.await();

        } while(!finished);

        lock.unlock();
    }

    @Override
    protected void onProgressUpdate(Result... result) 
    {
        // Treat this like onPostExecute(), do something with result

        // This is an example...
        if (result != whatWeWant && userWantsToTryAgain()) {
            runAgain();
        }
    }

    public void runAgain() {
        // Call this to request data from the server again
        tryAgain.signal();
    }

    public void terminateTask() {
        // The task will only finish when we call this method
        finished = true;
        lock.unlock();
    }

    @Override
    protected void onCancelled() {
        // Make sure we clean up if the task is killed
        terminateTask();
    }
}

もちろん、これはASyncTaskの従来の使用法よりも少し複雑であり、実際の進行状況レポートのためにpublishProgress()の使用をあきらめます。ただし、メモリが問題になる場合は、このアプローチにより、実行時にASyncTaskが1つだけヒープに残るようになります。


しかし、要点は、実行中にAsyntaskを再実行したくないということですが、これは、このタスクが終了してデータを正しく受信できなかったため、再度呼び出します。
Dayerman、2011年

実際には、この方法でASyncTaskを1回だけ実行し、onPublishProgressメソッド内でデータが正しいかどうかを確認できます(またはチェックをどこかに委任できます)。しばらくの間、このパターンを同様の問題に使用しました(多くのタスクが連続して発生し、ヒープサイズが危険にさらされています)。
seanhodges

しかし、その瞬間にサーバーが応答せず、10秒後にもう一度やり直したい場合はどうでしょうか。AsyncTaskは既に完了しています、正確ですか?それをもう一度呼び出す必要があります
Dayerman

私が何を意味するかを説明するためにいくつかのサンプルコードを追加しました。ASyncTaskは、結果に満足して「terminateTask()」を呼び出した場合にのみ終了します。
seanhodges

1
あなたが取得する場合IllegalMonitorStateExceptionにおけるrunAgain(によって呼び出さonProgressUpdate:この答えを参照)stackoverflow.com/a/42646476/2711811を。これはsignal()lock/ で囲む必要があることを示唆しています(そして私にとってはうまくいきました)unlock。これはをpublishProgress呼び出すタイミングに関係している可能性がありますonProgressUpdate
アンディ、

2

同じ問題がありました。私の場合、私はonCreate()とでやりたいタスクがありますonResume()。そこで、Asynctaskを静的にして、インスタンスを取得します。今でも同じ問題があります。

だから私がonPostExecute()でやったことはこれです:

instance = null;

静的getInstanceメソッドをチェックインして、インスタンスがnullでないことを確認します。それ以外の場合は作成します。

if (instance == null){
    instance = new Task();
}
return instance;

postExecuteのメソッドはインスタンスを空にして再作成します。もちろん、これはクラスの外で行うことができます。


1

ローテーションタスクを静的にしました。これにより、ローテーションの変更時にUIスレッドへのアタッチ、デタッチ、および再アタッチが容易になりました。しかし、あなたの質問に戻るために、私がしていることは、スレッドが実行されているかどうかを確認するためのフラグを作成することです。スレッドを再起動したい場合、ローテーションタスクが実行されているかどうかを確認します。そうでない場合は、nullにしてから新しいエラーを作成します。これにより、表示されているエラーを回避できます。さらに、正常に完了すると、完了したローテーション対応タスクを無効にして、再び実行できるようにします。


0

はい、それは本当です、ドキュメントはAsyntaskを1つしか実行できないと述べています。

それを使用する必要があるたびに、インスタンス化する必要があります。

// Any time if you need to call her
final FirmwareDownload fDownload = new FirmwareDownload();
fDownload.execute("your parameter");

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