Josh
Josh

Reputation: 7415

Android problems with out-of-order multithreaded processing and dialogs

I seem to have consistent problems with timing threads and dialog windows. I've tried using a thread, or onCreate/onPrepare, or an AsyncTask to do some downloading/processing in the background. More often than not, when the background processing completes and dismisses the dialog window, control seems to return to the root thread (Activity/UI thread?) before the dialog is gone or the onPostExecute-like process is done. This makes me think I'm doing it incorrectly. Here is a typical structure (pseudo-code):

public class X {
  protected String result = null;
  protected ProgressDialog progressDialog;

  public void onCreate() {
    ...
    new XTask().execute();
    progressDialog.show();
    // result is null here, should be "hi"?

    // do things with result, like barf on a NPE...sigh
  }

  private class XTask extends AsyncTask {
    protected doInBackground() {
      // Get URL.
      // Look at contents, takes a few seconds.
      // Return the result (should get sent to onPostExecute).
    }
    protected onPostExecute(r) {
      result = r;
      progressDialog.dismiss();
    }
  }
}

I would think that, after doPostExecute sets result and dismisses the dialog, processing then continues in the onCreate method. However, result is often (not always) null at this point in onCreate. Is there a better way to do this? Is this just due to the general crappiness of the emulator?

Thanks.

Edit: To be more concrete, my task (in doInBackground) fetches a URL and does a little processing on the response. This process takes 1-3 seconds. Then, theoretically, onPostExecute sets X.result to what was fetched. I know the URL content is valid and the response is good (not null). The problem is that during those 1-3 seconds, control returns to onCreate, as if the progressDialog never lived at all (it doesn't get displayed in the emulator at all, but that's normal I guess).

I had thought that calling dialog.show() was a blocking method, i.e., the dialog appeared and that method wouldn't continue until it disappeared, but that doesn't seem to be the case. Either my progressDialog.dismiss() is getting called before it should, before setting X.result, or not getting called at all, or dismiss() is happening faster/before the assignment, or something else entirely is going wrong...

Changing the order of the execute and progressDialog doesn't help, nor does moving it into onPreExecute(). Strangely, onPostExecute doesn't get called until I return in onCreate. If I have a Thread.sleep loop after execute (I thought giving it time would help), it never finishes the task until that loop finishes and I return. e.g.:

new XTask().execute();
int z=0;
while (response == null && z < 50) {
  Thread.sleep(500);
  z++;
}
if (response == null) return;

The task's onPostExecute method doesn't get called until "return". Hmmm...maybe being in onCreate is affecting it.

Upvotes: 1

Views: 337

Answers (4)

Josh
Josh

Reputation: 7415

Putting code into the task's onPostExecute should work, a simple test suggests it will (for me). However, I ended up writing a different solution which also works. I put most of the code into a Handler on the activity, which separates it from the UI thread entirely. My onCreate simply shows the loading ProgressDialog window and that's it--it just sits there, "loading". The background task does its thing and when finished, sends a message to the handler. This message tells the handler to dismiss the loading dialog and populate the list. If there are errors, different messages are sent and the handler shows an error dialog.

Upvotes: 0

triggs
triggs

Reputation: 5900

A ProgressDialog is usually used to block user interaction during loading or heavy processing but the main UI thread will continue to execute.

If you wish to perform some operation on the result you must do it in either onPostExecute of XTask or after you have gotten the result in doInBackground.

private class XTask extends AsyncTask {

     @Override
     protected void onProgressUpdate(/*params*/){
          //modify UI
     }
     protected doInBackground() {
     // Get URL.
     // Look at contents, takes a few seconds.

     //Option A: Now have the result, do some other processing here
     //Cant modify UI components from here, If you need to modify a UI component from           
     //here call publishProgress() and modify the component in onProgressUpdate()

     // Return the result (should get sent to onPostExecute).
}
protected onPostExecute(r) {

     result = r;
     //Option B do some processing on the result
     //You can modify UI components from here
     progressDialog.dismiss();
}

}

Upvotes: 1

yorkw
yorkw

Reputation: 41126

I would think that, after doPostExecute sets result and dismisses the dialog, processing then continues in the onCreate method.

This is not true, when you call new XTask().execute() in UI thread, application create a worker thread and start running whatever you defied in AsyncTask.doInBackground() on this work thread, at this point (after calling new XTask().execute()), UI thread continue execute code after new XTask().execute().

The point you are talking about where your work thread finish and return to UI thread is AysncTask.onPostExecute(), this method is guaranteed to be called on UI thread after AsyncTask finish. this is the reason why it is called AsyncTask. Both of UI thread and work thread are running asynchronously.

If you want to make your UI thread blocked and wait for AsyncTask finish after XTask().execute(), you can to this:

XTask xTask = new XTask();
xTask.execute();
progressDialog.show();
xTask.get() // <-- this will make your UI thread blocked and wait for AsycnTask at this point
// result is null here, should be "hi"?

This is possible but not a good practice, as AsyncTask.get() will block execution on calling thread so probably get ANR exception.

To sum up
1. AysncTask.onPostExecute() is where process return from worker thread to UI thread, we don't care where and when it will be called on UI thread, we just need ensure it will be called on UI thread properly at some point in the future.
2. AsyncTask.get() by calling this method actually make you AsyncTask running synchronously with the calling thread.

Upvotes: 0

azharb
azharb

Reputation: 1009

I would show the progress dialog before triggering the AsyncTask. Normally, when an AsyncTask gets executed, it takes a while to finish itself and in that time, the rest of the task calling method has already run. But in your case the task returns instantly which is probably why the dialog shows up AFTER the postexecute gets called under the AsyncTask.

Upvotes: 0

Related Questions