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


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

#### 概要

JavaDocの[ClassOverView](http://developer.android.com/intl/ja/reference/android/os/AsyncTask.html)には要約するとこんなことが書かれていました

```
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#execute`callして非同期処理を実行する。

```java

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スレッドに進捗を返すことが出来る`onProgressUpdate`や`doInBackground `の実行前に事前処理を行う`onPreExecute`もあります。

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

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

```java
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`の第一引数である`sDefaultExecutor`は`ThreadPoolExecutor`という管理下のスレッドプールでRunnableを実行するクラスと、実行するRunnable貯めておくスタックを内包している`SerialExecutor`というクラスです。 
executeOnExecutor内では、`Executor#exec(Runnable)`によって、事前にFutureTask化された`doInBackground`がworkerスレッドで実行されます。このような仕組みで`doInBackground`が非同期処理を実現していたんですね。

```java
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
  return executeOnExecutor(sDefaultExecutor, params);
}
```    

```java
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)`がコールされるためです。

```java
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](http://developer.android.com/intl/ja/reference/java/util/concurrent/ThreadPoolExecutor.html)を御覧ください
複雑な部分は読み飛ばしてしまいましたが、AsyncTaskの実装を読んでみるのは意外と面白かったです。次はAsyncTaskLoaderも覗いてみたいですね。