【问题标题】:Android AsyncTask onPostExecute off of main ui threadAndroid AsyncTask onPostExecute 关闭主 ui 线程
【发布时间】:2013-01-16 23:30:28
【问题描述】:

我在使用 AsyncTask 和 onPostExecute 时遇到问题。我发现 onPostExecute 正在与主 ui 线程不同的线程上执行,这导致在我修改任何视图时发生 CalledFromWrongThreadException。

我输入了一些日志来查看 onPreExecute、doInBackground 和 onPostExecute 正在运行的线程。我会看到这样的结果...

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

我相信主 ui 线程 id 是 1,我希望 onPre 和 onPost 都在线程 1 上执行。我确保从 ui 线程创建并调用执行方法(例如在 onCreate 的活动)。

我注意到的另一件事是,稍后的异步任务将在与之前的异步任务 onPostExecute 方法(在本例中为线程 18)相同的线程上运行它们的 onPostExecute 方法。

现在为了解决这个问题,我将 onPostExecute 方法中的代码封装在对 runOnUiThread 的调用中,但我认为这是 hacky,我想解决真正的问题。

我没有想法!任何人有任何见解?我很乐意回答任何有助于进一步调查的问题!

编辑:

在代码中运行异步任务有两种方式。我想知道这些例子中的后者是否会导致一些奇怪的事情发生?

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!!!
        }
    }

}

上面看起来像是 AsyncTask 的普通用法,但我仍然看到即使在这样的简单情况下也会出现这个问题。下一个示例使用异步任务来运行其他异步任务。也许我不知道当异步任务被构造时会发生什么导致一些奇怪的行为?

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

也许这里发生的事情非常奇怪,而且根本不是异步任务的使用方式,无论哪种方式,都能找到问题的根源。

如果您需要更多上下文,请告诉我。

【问题讨论】:

  • 能否出示相关代码?
  • AsyncTask 完成之前,你不会去另一个Activity,是吗?
  • 不,我等到 asynctask 完成,然后在 onPostExecuteMethod 中启动一个活动。我将在我的问题中添加一些相关代码。
  • @fxfilmxf 我遇到了同样的问题。无法弄清楚并且正在使用相同的 runOnUiThread()。如果您从那以后找到了答案,请......谢谢。

标签: android multithreading android-asynctask


【解决方案1】:

我也遇到过同样的问题,原来问题出在 Flurry 3.2.1 上。但是,问题不仅限于 Flurry 库。

幕后的问题是第一次(当第一次加载应用程序时)从不是主 UI 线程的 looper 线程调用 AsyncTask。此调用将 AsyncTask 中的 sHandler 静态变量初始化为错误的线程 id,然后在所有后续 AsyncTask$onPostExecute() 调用中使用此 id。

为了解决这个问题,我在第一次加载应用程序时调用了一个空的(什么都不做)AsyncTask,只是为了正确初始化 AsyncTask。

【讨论】:

  • 太棒了。我在想这样的事情会导致问题,但我不知道 AsyncTask 中的静态变量。非常感谢!
【解决方案2】:

尝试使用:

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

}
});

并在run 函数中编写代码

【讨论】:

  • 感谢您的回复。你是在建议我把它放在 onPostExecute 方法中吗?如果是这样,我已经将我的 onPostExecute 代码包装在对 runOnUiThread 的调用中,它工作得很好。我更想弄清楚为什么 onPostExecute 会脱离主线程,据我了解,这不应该发生。
  • 因为 AsyncTask 是一个不同于主 UIThread 的线程。它将在主线程执行当前代码时在后台执行。它用于避免在执行需要长时间的代码(例如从 Internet 下载)时阻塞应用程序。如果 AsyncTask 从主线程执行,它将在应用程序完成代码执行时阻塞。我希望这对你来说很清楚^_^​​
  • 不幸的是!您应该从主线程执行异步任务,但异步任务将执行的所有工作都将发生在单独的线程中。工作完成后,调用 onPostExecute 并在主线程上执行,以便您可以修改 ui 元素等。我想知道为什么 onPostExecute 不在主线程上执行
  • 明白了。我试过你的代码,但它在 onPreExecute 和 onPostExecute 中产生了相同的线程 id,但在 doInBackground 中不同
  • 感谢您运行它!在大多数手机上,我可能有 99% 的时间得到相同的结果。在我测试的一部手机上,我可以在首次安装应用时可靠地重现,但一旦应用崩溃并重新打开,一切都会恢复正常。
【解决方案3】:

AsyncTask 被设计为在主线程中使用。您的问题是第二种情况,是您从后台线程调用 SingleTask 上的执行。在 RunnerTask 的 doInBackground 方法中调用它。然后从 RunnerTask 的后台线程运行 onPostExecute

为您提供两种选择。

1:垃圾RunnerTask,从你的主线程执行SingleTasks,它们都会并行运行,你不知道哪个先完成,但是在主线程上调用onPreExecute和onPostExecute

2:将SingleTask 丢弃,改为将它们定义为Runnables,然后您可以在RunnerTask 的doInBackground 中依次运行它们。它们都将按照您调用 Run 的顺序在 RunnerTask 的后台线程中运行。完成后,RunnerTask 的 onPostExecute 在主线程上运行。

【讨论】:

    【解决方案4】:

    我刚刚尝试了您的代码,并且 onPreExecute 和 onPostExecute 确实在同一个线程上运行,您如何输出线程 id ?试试:

    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()));
    

    附:应该是:

    new SomeAsyncTask().execute();
    

    private class SomeAsyncTask extends AsyncTask<String, Void, Integer> { ... }
    

    【讨论】:

    • 感谢您的编辑,这只是我写这篇文章时的一些错别字,我会修复它们。我使用 Thread.currentThread().getId() 以与您描述的方式相同的方式输出线程 ID。在我测试的大多数手机上,id 都是你所期望的,但我可以可靠地在我拥有的 Galaxy s2 上重现该问题。我还在崩溃报告中看到 CalledFromWrongThreadException 发生在其他用户的手机上。
    • 也许在你的 asyntask 运行时活动被破坏并重新创建?例如由于方向变化。这里有两个很好的主题/文章:
    【解决方案5】:

    您实际上是从 RunnerTask 的 doinbackground 方法执行 SingleTask,这是不正确的,因为 asynctask 应仅从主线程执行。您需要重新研究从 RunnerTask 运行一组 SingleTask 的逻辑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多