Отменить AsyncTask через некоторое время

Это может быть дублированный вопрос, но я не нашел то, что искал. Я вызываю 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); но это тоже не работает.

Может ли кто-нибудь показать мне, как я могу это сделать или как использовать isCancelled() в doInBackground.

Благодарю.

Ответы

Ответ 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, вам нужно запустить задачу cancle через определенное время (= таймаут, в этом случае 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());

Ответ 2

Вы должны выполнить проверку времени в другом потоке.

В настоящее время вы выполняете: выполнение dc.collectData(query) (в фоновом режиме), и как только оно будет готово, вы проверяете, следует ли отменять. Поэтому, если запрос занимает 1 минуту, вы выполните отмену проверки через 1 минуту, что уже слишком поздно.

Что вы можете сделать, так это запланировать TimerTask, который должен запускаться через 30 секунд после LoadData(). Execute(), и если задание таймера запущено, вы можете отменить AsyncTask (если он все еще запущен)

Ответ 3

Я бы перевел это на проблему async/await, сделав все дорогие методы как методы async.

Сначала измените DataCollector collectData (query) на collectDataAsync (запрос). (Если вы не можете изменить DataCollector, там есть работа, чтобы обернуть его в функцию лямбда или что-то подобное).

Во-вторых, измените doInBackground как задачу async, примерно так:

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, вы хотите использовать cancelationToken. Я использую это, чтобы решить вашу проблему fooobar.com/questions/28721/....

Обратите внимание: теперь doInBackgroundAsync является асинхронным, поэтому он немного меняет способ его использования.

Надеюсь, поможет.

Ответ 4

попробуй это:

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

    // ...

}

Предоставлено и для более подробной информации см. Этот ответ.

Ответ 5

Короткий ответ: вы НЕ МОЖЕТЕ отменить AsyncTask после его запуска. Что вы можете сделать, это вставить цикл внутри doInBackGround() который будет проверять isCancelled() и если в будущем он будет установлен как true, возвращайте значение из функции (которое, в свою очередь, вызовет onPostExecute() если у вас есть определил его);

Обратите внимание, что только потому, что вы не можете остановить AsyncTask, это не означает, что ОС не отменит его, если он неактивен в памяти. Вы должны иметь это в виду, если вы выполняете важные задачи в AsyncTask (те, которые вы хотите выполнить на 100%). Если это так, лучше использовать службу - компонент, который автоматически будет убит и перезапущен ОС по мере необходимости.