Experiments Never Fail

AsyncTask を cancel した時の動き【その3】

さて前回は、AsyncTask の doInBackground 内で isCancelled をチェックして処理を中断する方法を確認しました。

しかし前回の処理では、カウンタ値が 101 (期待するのは 100) になってしまいました。

今回は、AsyncTask というよりもマルチスレッド処理では必須な排他制御を行います。

またいきなり回答なんですが、前回のコードを以下のように修正します。

AsyncTask のテストプログラム3

public class AsyncTestActivity extends Activity {
private int count = 0;
private MyTask task = null;
private Object objLock = new Object();

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Button btn = (Button)findViewById(R.id.Button01);
btn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {

// タスク A を実行
task = new MyTask("taskA");
task.execute((Void)null);

// 5秒待つ
SystemClock.sleep(5000);

// タスク A を中断
task.cancel(true);

// タスク B を実行
task = new MyTask("taskB");
task.execute((Void)null);
}
});
}

/** 非同期で加算を行う内部クラス */
class MyTask extends AsyncTask {
private String name = "";

MyTask(String name) {
this.name = name;
}

/** 非同期処理 */
@Override
protected Long doInBackground(Void... params) {
long start = System.currentTimeMillis();

// 複数スレッドで同時に処理されないように保護する。
synchronized (objLock) {
// count を 0~100 まで 100ms 毎に加算する処理
count = 0;
for (int i = 0; i < 100; i++) {
// キャンセルされたら抜ける
if (this.isCancelled()) {
return 0;
}

SystemClock.sleep(100);
count++;
}

// 処理にかかった時間を返す
return System.currentTimeMillis() - start;
}
}

/** doInBackground が終わったら呼び出される。 */
@Override
protected void onPostExecute(Long result) {
// 結果を表示 "タスク名 - カウンタ値 - 処理時間ms"
Toast.makeText(AsyncTestActivity.this,
name + " - " + String.valueOf(count) + " - "
+ String.valueOf(result) + "ms",
Toast.LENGTH_LONG).show();
}


/** cancel() がコールされると呼び出される。 */
@Override
protected void onCancelled() {
// 結果を表示 "タスク名 - cancel() が呼ばれました。"
Toast.makeText(AsyncTestActivity.this,
name + " - cancel() が呼ばれました。",
Toast.LENGTH_LONG).show();
}
}
}

AsyncTestActivity で objLock を定義・生成し、doInBackground の処理を synchronized(objLock) で囲みます。

こうすることで、囲まれた内部処理は、複数スレッドから並列処理されなくなります(あとから実行されたスレッドは、synchronized の箇所で待つことになります)。

この内容は、AsyncTask というより Java (というより一般的なプログラム言語) のイロハだと思いますので、詳しい説明は、他のサイトに譲ります。

このプログラムの実行結果は次のようになります。

taskA - cancel() が呼ばれました。
taskB - 100 - 100030ms

今度は、taskB の結果が確実に 100 になります。synchronized によって、taskA が処理されている間(キャンセルされるまで)は taskB は待っているため、正しくカウンタが初期化・加算されます。

という事で 前々回、前回 からのまとめです。

これで AsyncTask のキャンセル処理がだいたい理解できたかなーと思います。 次は、AsyncTask.cancel() の引数「mayInterruptIfRunning」について突っ込んでみたいと思います。true と false で何か変わるの?

published at tags: Android Java