7

I'm having an issue with AsyncTask and onPostExecute. I am finding that onPostExecute is executing on a different thread than the main ui thread, which is causing a CalledFromWrongThreadException to happen when I modify any views.

I put in some logging to see what threads onPreExecute, doInBackground, and onPostExecute are running on. I would see a result like this...

onPreExecute ThreadId: 1
doInBackground ThreadId: 25
onPostExecute ThreadId: 18

I believe the main ui thread id is 1 and I would expect both onPre and onPost to both execute on thread 1. I am making sure to create and also call the execute method from the ui thread (for example in onCreate of an Activity).

Another thing to note that I have noticed is that later async tasks will run their onPostExecute method on the same thread as previous async task onPostExecute methods (in this case thread 18).

Right now in order to get around this I am wrapping the code in my onPostExecute methods in a call to runOnUiThread, but I think this is hacky and would like to get to the real issue.

I am out of ideas! Any one have any insight? I'm happy to answer any questions that could helper with further investigation!

EDIT:

There are two ways that async tasks are being run in the code. I am wondering if the latter in these examples is causing something weird to happen?

public class SomeActivity extends Activity {
   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.main_layout);

       new SomeAsyncTask().execute();
   }

   private class SomeAsyncTask extends AsyncTask<String, Void, Integer> {
        @Override
        public void onPreExecute() {
            Thread.currentThread().getId() // 1
            //Show a dialog
        }

        @Override
        public Integer doInBackground(String... params) {
            Thread.currentThread().getId() // 25
            return 0;
        }

        @Override
        public void onPostExecute(Integer result) {
            Thread.currentThread().getId() // 18
            //hide dialog
            //update text view -> CalledFromWrongThreadException!!!
        }
    }

}

The above seems like a vanilla use of AsyncTask, but I still see this issue occurring even in simple cases like this. The next example uses an async task to run other async tasks. Maybe there is something I don't know about what happens when an async task gets constructed that is causing some weird behavior?

public class SomeActivity extends Activity implements TaskRunner.OnFinishListener {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);

        TaskRunner taskRunner = new TaskRunner();
        taskRunner.setOnFinishListener(this);
        taskRunner.addTask(new SingleTask());
        taskRunner.addTask(new SingleTask());
        taskRunner.execute();
    }

    @Override
    public void onTaskFinish(List<Integer> results) {
       //Thread id is 18 when it should be 1
       //do something to a view - CalledFromWrongThreadException!!
    }

}

//In a different file
public class SingleTask extends AsyncTask<String, Void, Integer> {
    //This is a an async task so we can run it separately as an asynctask
    //Or run it on whatever thread runnerExecute is called on
    @Override
    public Integer doInBackground(String... params) {
        return runnerExecute(params);
    }

    //Can be called outside of doInBackground
    public Integer runnerExecute(String... params) {
        //some long running task
        return 0;
    }
}

//In a different file
public class TaskRunner {

    private List<SingleTask> tasks;
    private OnFinishListener onFinishListener;

    public interface OnFinishListener {
        public void onTaskFinish(List<Integer> results);
    }

    public TaskRunner() {
        this.tasks = new ArrayList<SingleTask>();
    }

    public void setOnFinishListener(OnFinishListener listener) {
        this.onFinishListener = listener;
    }

    public void addTask(SingleTask task) {
        tasks.add(task);
    }

    public void executeTasks() {
        new RunnerTask().execute((SingleTask[]) tasks.toArray());
    }

    //Calls the runnerExecute method on each SingleTask
    private class RunnerTask extends AsyncTask<SingleTask, Integer, List<Integer>> {
        @Override
        public void onPreExecute() {
            //Runs on thread 1
        }

        @Override
        public List<Integer> doInBackground(SingleTask... params) {
            //Runs on arbitrary thread
            List<Integer> results = new ArrayList<Integer>();
            for(SingleTask task : params) {
                int result =task.runnerExecute(task.getParams());
                results.add(result);
            }
            return results;
        }

        @Override
        public void onPostExecute(List<Integer> results) {
            //Runs on thread 18
            onFinishListener.onTaskFinish(results);
        }
    }
}

Maybe what is going on here is just super weird, and not at all how async tasks are meant to be used, either way it would be nice to get to the bottom of the issue.

Let me know if you need any more context.

fxfilmxf
  • 582
  • 6
  • 13
  • Can you show the relevant code? – Cat Jan 16 '13 at 23:49
  • You aren't going to another `Activity` before `AsyncTask` is finished, are you? – codeMagic Jan 17 '13 at 00:11
  • No, I wait until the asynctask is complete and then start an activity in the onPostExecuteMethod. I'll add some relevant code to my question. – fxfilmxf Jan 17 '13 at 00:16
  • @fxfilmxf I am experiencing the same issue. Cannot figure it out and am using the same runOnUiThread(). If you've found the answer since, please.... Thanks. – OferR Aug 17 '13 at 04:28
  • 1
    This seems to be the problem solver : [http://stackoverflow.com/a/18479289/2519412][1] [1]: http://stackoverflow.com/a/18479289/2519412 – shimi_tap May 15 '14 at 14:55

5 Answers5

4

I have been experiencing the same problem and it turned out the the issue was using Flurry 3.2.1. However, the issue is not limited to the Flurry library.

The issue behind the scenes is having the first ever (when the app is loaded for the first time) AsyncTask call from a looper thread which is not the Main UI thread. This call initializes a sHandler static variable in AsyncTask to the wrong thread id, and this id is then used in all subsequent AsyncTask$onPostExecute() calls.

To solve the problem, I call an empty (do-nothing) AsyncTask on first app load, just to initialize AsyncTask correctly.

OferR
  • 1,634
  • 19
  • 21
  • Awesome. I was thinking something like this was causing the problem, but I didn't know about the static variable in AsyncTask. Thanks a bunch!! – fxfilmxf Sep 19 '13 at 16:56
1

try using:

getBaseContext().runOnUiThread(new Runnable()
{
@override
public void run()
{

}
});

and write your code inside the run function

KhalidTaha
  • 453
  • 1
  • 5
  • 11
  • Thanks for your response. Are you suggesting I put this in the onPostExecute method? If so, I am already wrapping my onPostExecute code in a call to runOnUiThread which works just fine. I am more trying to figure out why onPostExecute is running off of the main thread, which to my understanding, isn't supposed to happen. – fxfilmxf Jan 16 '13 at 23:42
  • because AsyncTask is a different Thread than the main UIThread. It will be executed in the Background while the main Thread is executing the current code. It is used in order to avoid blocking the application while executing a code that needs a long time such as Downloading from Internet. If the AsyncTask is executed from the main thread, it would block the application while it completes the executing of the code. I wish that is clear for you ^_^ – KhalidTaha Jan 16 '13 at 23:49
  • Not quite unfortunately! You are supposed to execute an asynctask from the main thread, but all the work that the async task will do will happen a separate thread. After that work is finished onPostExecute is called and is meant to be executed on the main thread so you can modify ui elements etc. I'm wondering why onPostExecute isn't executing on the main thread – fxfilmxf Jan 17 '13 at 01:11
  • understood. I tried your code but it produced the same thread id in onPreExecute and in onPostExecute , but different in doInBackground – KhalidTaha Jan 17 '13 at 01:21
  • this was my result in the LogCat 01-17 01:22:03.542: D/Main: 1 01-17 01:22:03.542: D/PreExecute: 1 01-17 01:22:03.562: D/doInBackground: 102 01-17 01:22:03.613: D/PostExecute: 1 – KhalidTaha Jan 17 '13 at 01:23
  • 1
    Thanks for running that! I get the same result too probably 99% of the time on most phones. There is one phone that I test on that I can reliable reproduce on first install of the app, but once the app crashes and reopens everything goes back to normal. – fxfilmxf Jan 17 '13 at 02:19
1

The AsyncTask is designed to be used from the main thread. Your problem is the second case, and is that you call execute on the SingleTask from a background thread. You call it in the doInBackground method of RunnerTask. The onPostExecute is then run from the backgroundthread of RunnerTask

Two options for you.

1: Trash RunnerTask, and execute the SingleTasks from you main thread, they'll all run in parallell and you won't know which finishes first, but onPreExecute and onPostExecute is called on the main thread

2: Trash the SingleTask and define them as Runnables instead, then you can run them in sequence in the RunnerTask's doInBackground. They'll all run in the background thread of RunnerTask, in the order you call Run. When it is finished, the onPostExecute of RunnerTask is run on the main thread.

Tore Rudberg
  • 1,594
  • 15
  • 16
0

i just tried your code and onPreExecute and onPostExecute does run on the same thread, how do you output the thread id ? try:

Log.d("THREADTEST","PRE"+Long.toString(Thread.currentThread().getId()));

Log.d("THREADTEST","BACKGROUND"+Long.toString(Thread.currentThread().getId()));

Log.d("THREADTEST","POST"+Long.toString(Thread.currentThread().getId()));

P.S. it should be:

new SomeAsyncTask().execute();

and

private class SomeAsyncTask extends AsyncTask<String, Void, Integer> { ... }
Suau
  • 4,628
  • 22
  • 28
  • Thanks for the edits, it just some typos as i was writing this up, I'll fix them. I'm outputting the thread id in the same manner that you are describing, with Thread.currentThread().getId(). On most phones I test on, the ids are what you would expect, but I can reliably repro the issue on a galaxy s2 I have. I also see CalledFromWrongThreadException happening on other users' phones in crash reports. – fxfilmxf Jan 17 '13 at 16:55
  • maybe the activity gets destroyed and recreated while your asyntask is running ? e.g. due to orientation changes. here are two good threads/articles on it: – Suau Jan 18 '13 at 08:40
  • @fxfilmxf http://stackoverflow.com/questions/3821423/background-task-progress-dialog-orientation-change-is-there-any-100-working http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ – Suau Jan 18 '13 at 08:41
0

you are actually executing the SingleTask from RunnerTask's doinbackground method which is incorrect as asynctask should be executed from a main thread only. You need to relook into the logic which runs the set of SingleTasks from RunnerTask.

siva
  • 1,850
  • 13
  • 14