取消AsyncTask的执行时间

15

这可能是一个重复的问题,但我没有找到我要找的。 我在UI活动中调用了一个AsyncTask new LoadData().execute(); ,在doInBackground中我调用了一个需要时间的方法。如果数据在一定时间内没有返回,我想中断这个线程。 以下是我尝试实现此操作的代码。

class LoadData extends AsyncTask<String, String, String>
{
    @Override
    protected void onPreExecute() {
    super.onPreExecute();
    startTime = System.currentTimeMillis();
    }
    protected String doInBackground(String... args)
    {

        DataCollector dc = new DataCollector();
        data = dc.collectData(query);
        //Here I check if the time is greater than 30 seconds then cancel
        if(((System.currentTimeMillis()-startTime)/1000)>30)
        {
           cancel(true);
        }
    return null;
    }
}

但这并不会在30秒后停止任务,实际上它需要更多的时间。 我也尝试了get(long timeout, TimeUnit unit);,但那也不起作用。

有人能告诉我如何做或者如何在doInBackground中使用isCancelled()吗?

谢谢。


也许这个答案可以帮到你:https://dev59.com/um855IYBdhLWcg3woVw_#11191070。 - jbarrameda
5个回答

12

你需要一个能在一定时间后取消你的任务的线程。这个线程可以像这样:

public class TaskCanceler implements Runnable{
    private AsyncTask task;

    public TaskCanceler(AsyncTask task) {
        this.task = task;
    }

     @Override
     public void run() {
        if (task.getStatus() == AsyncTask.Status.RUNNING )
            task.cancel(true);
     }
}

当你调用AsyncTask时,你需要在一定时间(即超时时间,在本例中为20秒)后运行取消任务。

private Handler handler = new Handler();
private TaskCanceler taskCanceler;
...
LoadData task = new LoadData();
taskCanceler = new TaskCanceler(task);
handler.postDelayed(taskCanceler, 20*1000);
task.execute(...)

如果您在取消或完成时清理它,那么这是一个好主意。

if(taskCanceler != null && handler != null) {
     handler.removeCallbacks(taskCanceler);
}

当然,您可以将此包装在AsyncTask的自定义实现中。我已经多次使用这种模式,它非常有效。需要注意的一点是,在极少数情况下,处理程序不会启动,我怀疑如果您在错误的上下文中创建它,则无法在某些情况下生存,因此我强制将处理程序放在UI线程中:handler= new Handler(Looper.getMainLooper());


3
您需要在不同的线程上进行时间检查。
您当前的做法是: 在后台执行dc.collectData(query),然后一旦准备好了就检查是否应该取消。因此,如果查询需要1分钟,您将在1分钟后进行取消检查,这已经太晚了。
您可以计划一个TimerTask,在LoadData().execute()之后30秒运行,并且如果TimerTask被运行,您可以取消AsyncTask(如果它仍在运行)。

2
我会将其翻译为一个异步/等待问题,将所有昂贵的方法转换为异步方法。
首先,将DataCollector的collectData(query)修改为collectDataAsync(query)。(如果您无法修改DataCollector,则可以使用一些解决方法来将其包装在lambda函数或类似的东西中)。
第二,将doInBackground更改为异步任务,类似于以下代码:
protected async Task<String> doInBackgroundAsync(String... args)
{
    DataCollector dc = new DataCollector();
    int timeout = 1000;
    var task = dc.collectDataAsync(query);
    if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
        // task completed within timeout
        data = task.Result;
    } else { 
        // timeout logic
    }
}

基本上,在doInBackgroundAsync内有两个任务:collectDataAsync和延迟任务。您的代码将等待较快的那个,然后您就知道哪一个是,从而可以做出相应的反应。
如果您还需要取消collectDataAsync任务,则需要使用cancellationToken。我使用这个来解决您的问题 https://dev59.com/um855IYBdhLWcg3woVw_#11191070
请注意,现在doInBackgroundAsync是异步的,因此使用方式有所改变。
希望它能帮助您。

如果你认为这个问题是重复的,你应该标记为重复,而不是发布另一个问题的链接。 - Dave Chen
我修改了我的回答。我不认为这是一个重复的问题,希望现在清楚了。 - jbarrameda

1

简短回答是一旦启动AsyncTask,就无法取消它。你可以在doInBackGround()里面插入一个循环,该循环将检查isCancelled()是否设置为真,并且如果它在未来的某个时刻被设置为真-从函数中返回一个值(这将依次调用onPostExecute()如果你已经定义了它);

请注意,仅仅因为您无法停止AsyncTask并不意味着操作系统不会在内存不足时取消它。如果在AsyncTask中执行重要任务(您希望执行100%),则应将此纳入考虑。如果是这样,请使用Service - 这是一个组件,操作系统根据需要自动关闭和重新启动。


1
实际上,我正在通过套接字连接从外部传感器收集数据。有时由于连接问题或传感器离线,该过程可能需要很长时间,因此我想中断任务。你说它无法取消,那么我们为什么有cancel(boolean state); get(long timeout,TimeUnit unit);方法可用? - AL̲̳I

0

试试这个:

public class MyTask extends AsyncTask<Void, Void, Void> {

    private volatile boolean running = true;
    private final ProgressDialog progressDialog;

    public MyTask(Context ctx) {
        progressDialog = gimmeOne(ctx);

        progressDialog.setCancelable(true);
        progressDialog.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                // actually could set running = false; right here, but I'll
                // stick to contract.
                cancel(true);
            }
        });

    }

    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }

    @Override
    protected void onCancelled() {
        running = false;
    }

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

        while (running) {
            // does the hard work
        }
        return null;
    }

    // ...

}

请参考此处获取更多详细信息。


1
这是正确的,但它将允许用户始终取消作业,并且不会在30秒后自动取消。 - peshkira
1
你可以在doInBackground中使用一个计时器,设置一些时间比如30秒,然后在计时结束后调用post cancel()方法。这将在任何时候停止你的异步任务。 - Shruti
@Shruti:感谢您的参考,但我不想允许用户取消操作。而 cancel(true) 对我来说似乎不起作用。我该如何使用 get(long timeout, TimeUnit unit);,因为它听起来更可行。 - AL̲̳I

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