【问题标题】:Cancel AsyncTask after some time一段时间后取消 AsyncTask
【发布时间】:2013-06-26 08:41:21
【问题描述】:

这可能是一个重复的问题,但我没有找到我要找的东西。 我在 UI 活动new LoadData().execute(); 中调用了一个 AsyncTask,在 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()。

谢谢。

【问题讨论】:

标签: java android android-asynctask


【解决方案1】:

您需要一个线程在一定时间后取消您的任务。该线程可能如下所示:

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 的自定义实现中。我已经多次使用这种模式,它就像一个魅力。需要注意的一点是,在极少数情况下,处理程序不会启动,我怀疑如果您在错误的上下文中创建它,它在某些情况下将无法生存,所以我强制处理程序成为带有handler= new Handler(Looper.getMainLooper());

的 UI 线程>

【讨论】:

    【解决方案2】:

    您必须在不同的线程上进行时间检查。

    您当前所做的是:执行dc.collectData(query)(在后台),一旦准备就绪,您检查是否应该取消。因此,如果查询需要 1 分钟,您将在 1 分钟后进行取消检查,这已经太晚了。

    你可以做的是安排一个 TimerTask,它应该在 LoadData().execute() 之后运行 30 秒,如果计时器任务正在运行,你可以取消 AsyncTask(如果它仍在运行)

    【讨论】:

      【解决方案3】:

      我会将其转化为 async/await 问题,使所有昂贵的方法都成为异步方法。

      首先,将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 任务,那么您需要使用 cancelToken。 我用它来解决你的问题https://stackoverflow.com/a/11191070/3307066

      请注意,现在 doInBackgroundAsync 是异步的,所以它改变了一点使用它的方式。

      希望对你有帮助。

      【讨论】:

      • 如果您认为这个问题是重复的,您应该将其标记为重复,而不是发布指向另一个问题的链接。
      • 我修改了我的答案。我不认为是重复的,希望现在清楚了。
      【解决方案4】:

      简短的回答是,一旦 AsyncTask 启动,您就无法取消它。您可以做的是在doInBackGround() 中插入一个循环,它将检查isCancelled(),如果它在将来的某个时间设置为true - 从函数返回一个值(如果你有,它将依次调用onPostExecute()定义它);

      请注意,不能停止 AsyncTask 并不意味着如果内存不足操作系统不会取消它。如果您在 AsyncTask 中执行基本任务(您希望 100% 执行的任务),您应该牢记这一点。如果是这样,最好使用Service - 一个由操作系统根据需要自动终止和重新启动的组件。

      【讨论】:

      • 实际上我通过套接字连接从外部传感器收集数据有时是因为连接问题或传感器离线,这个过程可能需要很长时间,这就是我想中断任务的原因。你说它不能取消,那为什么我们有cancel(boolean state); get(long timeout, TimeUnit unit); 方法可用?
      【解决方案5】:

      试试这个:

      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;
          }
      
          // ...
      
      }
      

      Courtesy 更多详细信息,请参阅此答案。

      【讨论】:

      • 这是正确的,但它会允许用户始终取消作业,并且不会在 30 秒后自动取消它
      • 您可以在后台使用计时器,设置一些时间,例如 30 秒,然后在 cancel() 发布后......它会随时停止您的异步任务
      • @Shruti:感谢您的参考,但我不想让用户取消操作。 cancel(true) 似乎对我不起作用。我该如何使用get(long timeout, TimeUnit unit);,因为听起来更可行。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-08
      • 1970-01-01
      • 1970-01-01
      • 2019-04-28
      相关资源
      最近更新 更多