LoginSignup
23
23

More than 5 years have passed since last update.

AsyncTaskの非同期処理はどのように実現しているのか

Last updated at Posted at 2016-01-10

最近は非同期処理の殆どをRxJava使って楽してるので、AsyncTaskの事はすっかり忘れてしまっていたのですが、改めて調べてみたので、メモ程度ですがご紹介します。(API Lebel 23の実装を前提に書いています。古いバージョンでは実装が異なる部分もあるそうです。 AndroidバージョンでのAsyncTaskの注意点 )

概要

JavaDocのClassOverViewには要約するとこんなことが書かれていました

AsyncTaskはバックグラウンドで処理を実行し、ThreadまたはHandlerを操作することなくUIスレッドに結果を公開することが出来ます。
AsyncTaskはThreadやHandlerを扱うためのヘルパークラスとしてデザインされており、一般的なスレッドフレームワークとして作られていません。
AsyncTasksは最大で数秒程度の処理時間のタスクに利用すべきで、長時間実行中のスレッドを維持しておきたいなら `java.util.concurrent`パッケージのThreadPoolExecutorとFutureTaskなどを利用することをおすすめします。

長時間かかる処理にAsyncTaskを利用すべきでないということは確かによく耳にしますね。

基本的な使い方

AsyncTaskはabstruct classで最低限2つの事を行う必要があります。
1. abstruct methodの AsyncTask#doInBackgroundに非同期で実行したい処理を記述する。
2. AsyncTask#executecallして非同期処理を実行する。


new AsyncTask<String, Integer, Long>() {
    @Override
    protected Long doInBackground(String... params) {
        // ここにバックグラウンドで行いたい処理を記述し、結果をreturnする。
        return 2l;
    }
}.execute("wow","wow"); //非同期処理に対して引数を渡す。

AsyncTask#executeの引数はdoInBackgroundに可変長引数の形で渡されます。そしてdoInBackgroundの処理は、AsyncTaskによってworkerスレッド(UIスレッド以外のスレッド)内で実行されます。
結果を受け取りたい場合はonPostExecuteをオーバーライドすることでUIスレッド上で結果を受け取ることができます。他にもオプショナルな機能として、UIスレッドに進捗を返すことが出来るonProgressUpdatedoInBackgroundの実行前に事前処理を行うonPreExecuteもあります。

doInBackgroundの処理が非同期に実行される仕組み

AsyncTaskがどのように非同期処理を実現しているのか実装を追ってみます。
まずAsyncTaskのコンストラクタの中で、doInBackgroundの処理はFutureTask化されます。FutureTaskについては正しい説明が出来る自信はないですが、要は「Runnableでラップして、後に出てくるThreadPoolExecutorに渡せる形に変換している」という理解でとりあえず問題ないと思います。 簡略化したコンストラクタが以下のようになります。

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            Result result = doInBackground(mParams);
            return postResult(result);
        }
    };

    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
          postResultIfNotInvoked(get());
        }
    };
}

ここまでが、AsyncTaskのインスタンスが生成された時に行われる下準備です。
ここからは実行時の処理です。AsyncTask#executeがcallされることでAsyncTask#executeOnExecutorが内部で呼ばれます。executeOnExecutorの第一引数であるsDefaultExecutorThreadPoolExecutorという管理下のスレッドプールでRunnableを実行するクラスと、実行するRunnable貯めておくスタックを内包しているSerialExecutorというクラスです。
executeOnExecutor内では、Executor#exec(Runnable)によって、事前にFutureTask化されたdoInBackgroundがworkerスレッドで実行されます。このような仕組みでdoInBackgroundが非同期処理を実現していたんですね。

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
  return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
    onPreExecute();
    mWorker.mParams = params;
    exec.execute(mFuture);
    return this;
}

ちなみにdoInBackgroundの実行結果がUIスレッドに戻ってこれるのは、doInBackgroundをFutureTask化した際に、最後にpostResult(result)を呼ぶようになっていて、内部のInternalHandlerというHandlerにメッセージを送信され、それ経由でAsyncTask#onPostExecute(result)がコールされるためです。

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

おわり

結局、なぜ長時間の処理に向いてない理由がよくわからなかったので、あとで調べて追記したいと思います。
AsyncTask内部のThreadPoolExecutorはプール数がCPU数+1、KeepAliveTimeが1sで設定されています。なので例えばPool数を超えた処理が流れてきた場合、1sを超えた現在実行中のタスクは終了を待たず止められてしまう可能性があるからおすすめできないということでした。こちらも詳細はThreadPoolExecutorのJavadocを御覧ください
複雑な部分は読み飛ばしてしまいましたが、AsyncTaskの実装を読んでみるのは意外と面白かったです。次はAsyncTaskLoaderも覗いてみたいですね。

23
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
23