【问题标题】:AysncTask cancelling itself still calls onPostExecute()取消自身的 AsyncTask 仍然调用 onPostExecute()
【发布时间】:2012-04-15 16:22:05
【问题描述】:

doInBackground() 内部调用AsyncTask.cancel(true) 后,Android 调用onPostExecute(),而不是调用onCancelled()。但是as per the documentation

调用这个方法会导致onCancelled(Object)被调用 在doInBackground(Object[]) 返回后的 UI 线程上。 调用这个 方法保证onPostExecute(Object) 永远不会被调用

这是 Android 中的错误吗?

更多观察:

  1. 从任一线程调用cancel(false) 的工作方式在 文档。
  2. 从 UI 任务调用 cancel(true) 不会调用 onPostExecute(),也不会抛出下面 logcat 跟踪中看到的 InterruptedException
  3. 从任何线程调用cancel(false/true) 有时甚至在doInBackground() 返回之前调用onCancelled()。这显然违反了文档,which states

调用此方法将导致 onCancelled(Object) 被调用 在 UI 线程上doInBackground(Object[]) 返回后

代码:(在 Android 2.2 设备上测试)

protected Void doInBackground(Void... params) {
    Log.d(TAG, "started doInBackground()");
    while (!isCancelled()) {
        boolean ret = cancel(true);
        Log.d(TAG, "cancel() returned: " + ret);
    }
    Log.d(TAG, "returning from doInBackground()");
    return null;
}

Logcat 输出

04-15 21:38:55.519: D/MyTask(27597): started doInBackground()
04-15 21:38:55.589: W/AsyncTask(27597): java.lang.InterruptedException
04-15 21:38:55.589: W/AsyncTask(27597):     at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1254)
04-15 21:38:55.589: W/AsyncTask(27597):     at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:219)
04-15 21:38:55.589: W/AsyncTask(27597):     at java.util.concurrent.FutureTask.get(FutureTask.java:82)
04-15 21:38:55.589: W/AsyncTask(27597):     at android.os.AsyncTask$3.done(AsyncTask.java:196)
04-15 21:38:55.589: W/AsyncTask(27597):     at java.util.concurrent.FutureTask$Sync.innerCancel(FutureTask.java:293)
04-15 21:38:55.589: W/AsyncTask(27597):     at java.util.concurrent.FutureTask.cancel(FutureTask.java:75)
04-15 21:38:55.589: W/AsyncTask(27597):     at android.os.AsyncTask.cancel(AsyncTask.java:325)
04-15 21:38:55.589: W/AsyncTask(27597):     at com.example.test.TestActivity$MyTask.doInBackground(TestActivity.java:31)
04-15 21:38:55.589: W/AsyncTask(27597):     at com.example.test.TestActivity$MyTask.doInBackground(TestActivity.java:1)
04-15 21:38:55.589: W/AsyncTask(27597):     at android.os.AsyncTask$2.call(AsyncTask.java:185)
04-15 21:38:55.589: W/AsyncTask(27597):     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
04-15 21:38:55.589: W/AsyncTask(27597):     at java.util.concurrent.FutureTask.run(FutureTask.java:137)
04-15 21:38:55.589: W/AsyncTask(27597):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
04-15 21:38:55.589: W/AsyncTask(27597):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
04-15 21:38:55.589: W/AsyncTask(27597):     at java.lang.Thread.run(Thread.java:1096)
04-15 21:38:55.589: D/MyTask(27597): cancel() returned: true
04-15 21:38:55.589: D/MyTask(27597): returning from doInBackground()
04-15 21:38:55.659: D/MyTask(27597): onPostExecute()

【问题讨论】:

  • 为什么要从doInBackground 中取消AsyncTask?那没有意义。 cancel(...) 方法故意允许工作线程之外的代码(换句话说,在 UI 线程上)停止执行。如果doInBackground 中的代码出于某种原因需要自行终止,那么它应该只是return。如果您不希望 onPostExecute(...) 由于伪取消而执行某些操作,则返回 false 否则返回 true
  • @MisterSquonk,“cancel(...) 方法故意允许工作线程之外的代码停止执行。”文档并没有说它只能从 UI 线程调用。为什么不重用onCancelled() 的现有代码,而不是使用难看的变通方法?
  • 同意,文档并没有说它应该只从 UI 线程调用,但这似乎是最合乎逻辑的。我这么说的原因是文档确实说doInBackground 应该定期检查isCancelled() 以查看它是否需要停止自己的执行(作为调用cancel 的结果)。如果doInBackground 调用cancel 是正常的,那么isCancelled() 的意义何在?
  • @MisterSquonk,很公平。仍然为什么会出现这种人为的怪异行为?另外,我在“更多观察”下添加了第 3 点。
  • 我一直在寻找适用于 Android v2.2 的 AsyncTask 的源代码...grepcode.com/file/repository.grepcode.com/java/ext/… 但我无法找到答案。我可以看到 AsyncTask 使用 java.util.concurrent.FutureTask 这就是引发您发布的异常的原因。我暂时找不到它的来源。我怀疑某处有一个小错误,我同意优雅地使用onCancelled 会很好。也许编写自己的AsyncTask 版本可能是答案。

标签: android android-asynctask


【解决方案1】:
  1. 有一个例外,因为您调用了 cancel(true),它会向运行 doInBackground() 的线程发送一个中断 - 但是,在这种情况下,您是从 doInBackground() 中调用 cancel(true),从而导致线程立即向自身发送中断。

  2. 您的代码在 Android 2 上运行,但您引用了 Android 4 的文档。问题是 cancel() 上的行为在 Android 2 和 Android 4 之间发生了变化。

    Android 2.3.7 onPostExecute

    在 doInBackground 之后在 UI 线程上运行。指定的结果是 doInBackground 返回的值,如果任务被取消,则返回 null 或发生异常。

    Android 4.0.1 onPostExecute

    在 doInBackground 之后在 UI 线程上运行。指定的结果是 doInBackground 返回的值。如果出现以下情况,则不会调用此方法 任务已取消。

【讨论】:

    【解决方案2】:

    您应该返回 null 并在 onPostExecute 中处理返回。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多