Nguyễn Văn Quang
Nguyễn Văn Quang

Reputation: 33

Threading rules AsyncTask

AsyncTask has 5 threads rules:

There are a few threading rules that must be followed for this class to work properly:

  1. The AsyncTask class must be loaded on the UI thread. This is done automatically as of Build.VERSION_CODES.JELLY_BEAN.

  2. The task instance must be created on the UI thread.

  3. execute(Params...) must be invoked on the UI thread.

  4. Do not call onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...) manually.

  5. The task can be executed only once (an exception will be thrown if a second execution is attempted.)

However, I didn't understand rules 2 and 3 very well. I've tried them on the following code:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    log(Thread.currentThread().getName());

    new Thread(new Runnable() {
        @Override
        public void run() {
            log(Thread.currentThread().getName());
            Task task = new Task();
            task.execute();
        }
    }).start();
}

public class Task extends AsyncTask<Void, Void, Void> {

    @Override
    protected Void doInBackground(Void... voids) {
        return null;
    }
}

And this is the result:

09-15 21:27:10.179 3310-3310/com.xxx.test D/com.xxx.test.MainActivity: main
09-15 21:27:10.179 3310-3329/com.xxx.test D/com.xxx.test.MainActivity: Thread-264

I have a question: Why can I create the task instance and call the execute() method in another thread(Thread-264) besides the UI Thread (main)?

I read this post, but it don't explain why. Thanks so much!

Upvotes: 0

Views: 1081

Answers (2)

witoong623
witoong623

Reputation: 1199

There are 3 protect methods that user code can expect them to be run from UI

  1. onPreExecute()
  2. onProgressUpdate(Progress...)
  3. onPostExecute(Result)

Although onPreExecute() will be run on whatever thread calling execute(), the rest 2 methods will be run by Handler.

The Handler class will associate with the thread that create it, it allows user code to post Runable to run on that specific thread. Before AsyncTask came, user code who want update UI (which must be updated on UI thread) would have to create Handler on UI thread first and then post Runable to that Handler to execute their task on UI thread.

AsyncTask was design to simplify those tedious works, their internal static Handler is created by UI/main thread before your AsyncTask instance is created.

Even though you can use AsyncTask (except onPreExecute()) in worker thread, I recommend you to follow documentation and create/run AsyncTask on UI thread.

Upvotes: 0

Son Truong
Son Truong

Reputation: 14173

From Android official site

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread.

There are some points that we need to clarify.

  1. Our computation will be ran on a background thread
  2. Result of the computation will be published on the UI thread
  3. They do not prevent developers from creating or invoking AsyncTask from non-UI threads

Step 1: When you call

Task task = new Task();

Take a look into AsyncTask source code.

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
        ? getMainHandler()
        : new Handler(callbackLooper);

    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Result result = null;
            try {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                result = doInBackground(mParams);
                Binder.flushPendingCommands();
            } catch (Throwable tr) {
                mCancelled.set(true);
                throw tr;
            } finally {
                postResult(result);
            }
            return result;
        }
    };

    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            try {
                postResultIfNotInvoked(get());
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occurred while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };
}

First they create a handler which refer to handler of the UI thread, then create a Runnable which call doInBackground method (our computation here) and then returns a Future (will return result of the computation some point in the future).

Step 2: Then you call

task.execute();

Take a look into AsyncTask source code.

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
    if (mStatus != Status.PENDING) {
        switch (mStatus) {
            case RUNNING:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task is already running.");
            case FINISHED:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task has already been executed "
                        + "(a task can be executed only once)");
        }
    }

    mStatus = Status.RUNNING;

    onPreExecute();

    mWorker.mParams = params;

    exec.execute(mFuture);

    return this;
}

onPreExecute() will be called on calling thread which invoke the AsyncTask (in this case your anonymous thread). Then it executes the Future in its executor.

After the computation is completed, it will call postResult method.

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

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

    @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;
        }
    }
}

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

getHandler in this case refer to handler of the UI thread, so onPostExecute will always be called on UI thread.

Conclusion:

AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

Upvotes: 1

Related Questions