多次执行AsyncTask

130
在我的Activity中,我使用了一个继承自AsyncTask的类,并且有一个参数是该AsyncTask的实例。当我调用 mInstanceOfAT.execute("") 时一切正常。
但是,当我按下更新按钮并再次调用AsyncTask(如果网络工作未完成时),应用程序会崩溃。此时会出现一个异常,显示:

无法执行任务:任务已经被执行(一个任务只能执行一次)

我尝试对Asyctask的实例调用cancel(true),但它也不起作用。到目前为止唯一的解决方法是创建新的Asyntask实例。这样做是否正确?
谢谢。
5个回答

219

AsyncTask 实例只能使用一次。

相反,只需像这样调用你的任务:new MyAsyncTask().execute("");

来自 AsyncTask API 文档:

线程规则

必须遵循该类的一些线程规则才能正常工作:

  • 任务实例必须在 UI 线程上创建。
  • execute(Params...) 必须在 UI 线程上调用。
  • 不要手动调用 onPreExecute()onPostExecute(Result)doInBackground(Params...)onProgressUpdate(Progress...)
  • 任务只能执行一次(如果尝试进行第二次执行,将会抛出异常)。

2
我所说的我已经完成了,这是唯一的可能性吗?因为我想节省内存,而不是创建一个新对象。 - Dayerman
1
请参见https://dev59.com/BHE85IYBdhLWcg3wkkfK。 - Steve Prentice
@StevePrentice:如果我使用new task().execute(param)每x秒创建一个任务实例来向服务器发送数据,那么执行完成后垃圾回收器如何释放内存? - Ant4res
3
只要你不引用异步任务实例,垃圾回收将会释放内存。但是,如果你有一个正在进行的后台任务,可以考虑在doInBackground中使用循环来调用publishProgress更新进度。另一种方法是将任务放到后台线程中。这里有很多不同的方法,但没有详细信息,我们无法推荐其中任何一种方法。 - Steve Prentice

29

Steve Prentice在他的回答中详细阐述了使用ASyncTask的fire-and-forget实例的原因- 然而,虽然你在执行ASyncTask时受到限制,但是在线程运行时你可以自由地进行任何操作...

将可执行代码放在doInBackground()内的循环中,并使用并发锁来触发每次执行。您可以使用publishProgress()/onProgressUpdate()来检索结果。

示例:

class GetDataFromServerTask extends AsyncTask<Input, Result, Void> {

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition tryAgain = lock.newCondition();
    private volatile boolean finished = false;

    @Override
    protected Void doInBackground(Input... params) {

        lock.lockInterruptibly();

        do { 
            // This is the bulk of our task, request the data, and put in "result"
            Result result = ....

            // Return it to the activity thread using publishProgress()
            publishProgress(result);

            // At the end, we acquire a lock that will delay
            // the next execution until runAgain() is called..
            tryAgain.await();

        } while(!finished);

        lock.unlock();
    }

    @Override
    protected void onProgressUpdate(Result... result) 
    {
        // Treat this like onPostExecute(), do something with result

        // This is an example...
        if (result != whatWeWant && userWantsToTryAgain()) {
            runAgain();
        }
    }

    public void runAgain() {
        // Call this to request data from the server again
        tryAgain.signal();
    }

    public void terminateTask() {
        // The task will only finish when we call this method
        finished = true;
        lock.unlock();
    }

    @Override
    protected void onCancelled() {
        // Make sure we clean up if the task is killed
        terminateTask();
    }
}

当然,这比传统的ASyncTask用法稍微复杂一些,并且你放弃了使用 publishProgress() 进行实际进度报告。但如果内存是你考虑的因素,那么这种方法将确保在运行时只有一个ASyncTask存在于堆中。


但重点是我不想在异步任务运行时重新执行它,但如果它已经完成并且没有按照预期接收数据,则再次调用它。 - Dayerman
实际上,您只需按此方式执行一次ASyncTask,并且可以在onPublishProgress方法中检查数据是否正确(或将检查委托到其他地方)。我曾经为类似的问题使用过这种模式(在快速连续触发大量任务时容易出现堆大小问题)。 - seanhodges
但是如果此时服务器没有响应,我想在10秒后再次尝试怎么办?AsyncTask已经完成了,对吧?那么我需要再次调用它。 - Dayerman
我已经添加了一些示例代码来描述我的意思。只有在您对结果感到满意并调用“terminateTask()”后,ASyncTask才会完成。 - seanhodges
@seanhodges,我在实现这个解决方案时遇到了一些问题。我在这里创建了一个新的线程,请提供一些处理该异常的解决方案。 - priyanka kamthe
1
如果你在 runAgain(由 onProgressUpdate 调用)中遇到了 IllegalMonitorStateException,请参考这个答案:https://stackoverflow.com/a/42646476/2711811。它建议(并且对我有效)将 signal() 包围在 lock/unlock 中。这可能与 publishProgress 调用 onProgressUpdate 的时间有关。 - user2711811

2
我遇到了相同的问题。在我的情况下,我想在onCreate()onResume()中执行一个任务。所以我将我的Asynctask设置为静态,并从中获取实例。现在我们仍然有同样的问题。
因此,在onPostExecute()中我做了以下操作:
instance = null;

记住,在静态的getInstance方法中检查实例是否为空,如果为空则创建它:

if (instance == null){
    instance = new Task();
}
return instance;

postExecute方法将清空实例并重新创建。当然,这也可以在类外部完成。


1

我已经将我的旋转任务设置为静态,这有助于我在旋转更改时将它们附加、分离和重新附加到UI线程上。但回到你的问题,我的做法是创建一个标志来查看线程是否正在运行。当您想要重新启动线程时,我会检查旋转任务是否正在运行,如果是,我会弹出一个警告。如果不是,我将其设置为null,然后创建一个新的旋转任务,这将解决您所看到的错误。此外,在成功完成后,我会将已完成的旋转感知任务设置为null,以便再次使用。


0

是的,这是真的,文档中说只能执行一个Asyntask。

每次需要使用它时,您都需要实例化:

// Any time if you need to call her
final FirmwareDownload fDownload = new FirmwareDownload();
fDownload.execute("your parameter");

static class FirmwareDownload extends AsyncTask<String, String, String> {
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接