Cách lý tưởng để hủy AsyncTask đang thực thi


108

Tôi đang chạy các hoạt động tìm nạp tệp âm thanh từ xa và phát lại tệp âm thanh trong một chuỗi nền bằng cách sử dụng AsyncTask. Một Cancellablethanh tiến trình được hiển thị cho thời gian hoạt động tìm nạp chạy.

Tôi muốn hủy / hủy bỏ quá trình AsyncTaskchạy khi người dùng hủy bỏ (quyết định chống lại) hoạt động. Cách lý tưởng để xử lý một trường hợp như vậy là gì?

Câu trả lời:


76

Chỉ cần phát hiện ra rằng AlertDialogsboolean cancel(...);tôi đã sử dụng ở khắp mọi nơi thực sự không có gì. Tuyệt quá.
Vì thế...

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

    // ...

}

55
thay vì tạo cờ boolean để chạy, bạn không thể xóa nó và thực hiện điều này trong khi (! isCanceled ()) ???
confucius

36
Từ tài liệu về onCancelt (): "Chạy trên chuỗi giao diện người dùng sau khi hủy (boolean) được gọi và doInBackground (Đối tượng []) đã kết thúc." Điều này 'sau khi' có nghĩa là đặt cờ trong onCancell và đăng ký doInBackground không có ý nghĩa gì.
lopek

2
@confucius đúng vậy, nhưng theo cách này, luồng nền không bị gián đoạn, giả sử trong trường hợp tải lên hình ảnh, quá trình tải lên tiếp tục trong nền và chúng tôi không nhận được onPostExecute được gọi.
umesh

1
@DanHulme Tôi tin rằng tôi đã tham khảo đoạn mã được cung cấp trong câu trả lời, chứ không phải nhận xét của confucius (đúng).
lopek

4
Có, câu trả lời này không hoạt động . Trong doInBackground, hãy thay thế while(running)bằng while(!isCancelled())như những người khác đã nói ở đây trong phần nhận xét.
matt5784

76

Nếu bạn đang tính toán :

  • Bạn phải kiểm tra isCancelled()định kỳ.

Nếu bạn đang thực hiện một yêu cầu HTTP :

  • Lưu phiên bản của bạn HttpGethoặc HttpPostmột nơi nào đó (ví dụ: trường công cộng).
  • Gọi xong cancelthì gọi request.abort(). Điều này sẽ gây ra IOExceptionném bên trong của bạn doInBackground.

Trong trường hợp của tôi, tôi có một lớp trình kết nối mà tôi đã sử dụng trong các AsyncTasks khác nhau. Để đơn giản, tôi đã thêm một abortAllRequestsphương thức mới vào lớp đó và gọi phương thức này trực tiếp sau khi gọi cancel.


cảm ơn bạn, nó hoạt động, nhưng làm thế nào để tránh ngoại lệ trong trường hợp này?
begiPass

Bạn phải gọi HttpGet.abort()từ một chuỗi nền nếu không bạn sẽ nhận được android.os.NetworkOnMainThreadException.
Heath Borders

@wrygiel Nếu bạn đang thực hiện một yêu cầu HTTP, không nên cancel(true)làm gián đoạn yêu cầu? Từ tài liệu:If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
Storo

HttpURLConnection.disconnect();
Oded Breiner

Nếu bạn có một hoạt động tiêu thụ cpu trong AsyncTask, vì vậy bạn phải gọi cancel(true). Tôi đã sử dụng nó và nó hoạt động.
SMMousavi

20

Vấn đề là lệnh gọi AsyncTask.cancel () chỉ gọi hàm onCancel trong tác vụ của bạn. Đây là nơi bạn muốn xử lý yêu cầu hủy.

Đây là một nhiệm vụ nhỏ tôi sử dụng để kích hoạt một phương pháp cập nhật

private class UpdateTask extends AsyncTask<Void, Void, Void> {

        private boolean running = true;

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

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
            onUpdate();
        }

        @Override
        protected Void doInBackground(Void... params) {
             while(running) {
                 publishProgress();
             }
             return null;
        }
     }

2
Điều này sẽ hoạt động, nhưng về mặt logic khi bạn đang chờ phản hồi của máy chủ và bạn vừa thực hiện thao tác db, thì sẽ phản ánh đúng các thay đổi đối với hoạt động của bạn. Tôi đã viết một blog về điều đó, hãy xem câu trả lời của tôi.
Vikas

4
Như đã đề cập trong phần nhận xét về câu trả lời được chấp nhận, bạn không cần phải tạo runningcờ của riêng mình . AsyncTask có một cờ bên trong được đặt khi nhiệm vụ đã bị hủy bỏ. Thay thế while (running)bằng while (!isCancelled()). developer.android.com/reference/android/os/… Vì vậy, trong trường hợp đơn giản này, bạn không cần onCancelled()ghi đè.
ToolmakerSteve

11

Đơn giản: không sử dụng một AsyncTask. AsyncTaskđược thiết kế cho các hoạt động ngắn kết thúc nhanh chóng (hàng chục giây) và do đó không cần phải hủy bỏ. "Phát lại tệp âm thanh" không đủ điều kiện. Bạn thậm chí không cần một chuỗi nền để phát lại tệp âm thanh thông thường.


bạn có gợi ý rằng chúng tôi sử dụng luồng java thông thường và "hủy bỏ" luồng chạy bằng cách sử dụng biến boolean dễ bay hơi - cách Java thông thường không?
Samuh

34
Không xúc phạm Mike, nhưng đó không phải là một câu trả lời chấp nhận được. AsyncTask có một phương thức hủy bỏ và nó sẽ hoạt động. Theo như tôi có thể nói, nó không - nhưng ngay cả khi tôi làm sai, thì nên có một cách đúng đắn để hủy bỏ một nhiệm vụ. Phương pháp này sẽ không tồn tại nếu không. Và ngay cả các tác vụ ngắn cũng có thể cần hủy - Tôi có một Hoạt động trong đó nó bắt đầu AsyncTask ngay khi tải và nếu người dùng truy cập lại ngay sau khi mở tác vụ, họ sẽ thấy Buộc đóng một giây sau khi tác vụ kết thúc nhưng không có ngữ cảnh tồn tại để nó sử dụng trong onPostExecute của nó.
Eric Mill,

10
@Klondike: Tôi không biết "Mike" là ai. "nhưng đó không phải là một câu trả lời chấp nhận được" - bạn hoan nghênh ý kiến ​​của mình. "AsyncTask có một phương thức hủy và nó sẽ hoạt động." - Việc hủy bỏ luồng trong Java đã là một vấn đề trong ~ 15 năm. Nó không liên quan gì nhiều đến Android. Đối với kịch bản "Buộc đóng" của bạn, điều đó có thể được giải quyết bằng một biến boolean, mà bạn thử nghiệm onPostExecute()để xem liệu bạn có nên tiếp tục công việc hay không.
CommonsWare

1
@Tejaswi Yerukalapudi: Điều quan trọng hơn là nó sẽ không tự động làm bất cứ điều gì. Xem câu trả lời được chấp nhận cho câu hỏi này.
CommonsWare

10
Bạn phải kiểm tra phương thức isCancell định kỳ trong doInBackground của mình trên AsyncTask. Nó phải có trong tài liệu: developer.android.com/reference/android/os/...
Christopher Perry

4

Cách duy nhất để làm điều đó là kiểm tra giá trị của phương thức isCancell () và dừng phát lại khi nó trả về true.


4

Đây là cách tôi viết AsyncTask của mình
, điểm mấu chốt là thêm Thread.sleep (1);

@Override   protected Integer doInBackground(String... params) {

        Log.d(TAG, PRE + "url:" + params[0]);
        Log.d(TAG, PRE + "file name:" + params[1]);
        downloadPath = params[1];

        int returnCode = SUCCESS;
        FileOutputStream fos = null;
        try {
            URL url = new URL(params[0]);
            File file = new File(params[1]);
            fos = new FileOutputStream(file);

            long startTime = System.currentTimeMillis();
            URLConnection ucon = url.openConnection();
            InputStream is = ucon.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(is);

            byte[] data = new byte[10240]; 
            int nFinishSize = 0;
            while( bis.read(data, 0, 10240) != -1){
                fos.write(data, 0, 10240);
                nFinishSize += 10240;
                **Thread.sleep( 1 ); // this make cancel method work**
                this.publishProgress(nFinishSize);
            }              
            data = null;    
            Log.d(TAG, "download ready in"
                  + ((System.currentTimeMillis() - startTime) / 1000)
                  + " sec");

        } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                returnCode = FAIL;
        } catch (Exception e){
                 e.printStackTrace();           
        } finally{
            try {
                if(fos != null)
                    fos.close();
            } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                e.printStackTrace();
            }
        }

        return returnCode;
    }

1
Tôi thấy rằng chỉ cần gọi hủy (true) trên tác vụ không đồng bộ và kiểm tra isCancell () định kỳ hoạt động, nhưng tùy thuộc vào nhiệm vụ của bạn đang làm, có thể mất đến 60 giây trước khi nó được xen kẽ. Thêm Thread.sleep (1) cho phép nó được kết hợp ngay lập tức. (Async Task chuyển sang trạng thái Chờ và không bị hủy ngay lập tức). Cảm ơn vì điều đó.
John J Smith

0

Biến lớp AsyncTask toàn cầu của chúng tôi

LongOperation LongOperationOdeme = new LongOperation();

Và hành động KEYCODE_BACK làm gián đoạn AsyncTask

   @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            LongOperationOdeme.cancel(true);
        }
        return super.onKeyDown(keyCode, event);
    }

Nó làm việc cho tôi.


0

Tôi không thích buộc làm gián đoạn các tác vụ cancel(true)không đồng bộ của mình với một cách không cần thiết vì chúng có thể có các tài nguyên cần được giải phóng, chẳng hạn như đóng các ổ cắm hoặc luồng tệp, ghi dữ liệu vào cơ sở dữ liệu cục bộ, v.v. Mặt khác, tôi đã phải đối mặt với các tình huống trong đó nhiệm vụ không đồng bộ từ chối tự hoàn thành một phần thời gian, ví dụ: đôi khi khi hoạt động chính đang được đóng và tôi yêu cầu tác vụ không đồng bộ kết thúc từ bên trong onPause()phương thức của hoạt động . Vì vậy, nó không phải là vấn đề chỉ đơn giản là gọi điện running = false. Tôi phải đi đến một giải pháp hỗn hợp: cả hai cuộc gọi running = false, sau đó cung cấp cho tác vụ không đồng bộ một vài mili giây để hoàn thành và sau đó gọi một trong hai cancel(false)hoặc cancel(true).

if (backgroundTask != null) {
    backgroundTask.requestTermination();
    try {
        Thread.sleep((int)(0.5 * 1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    if (backgroundTask.getStatus() != AsyncTask.Status.FINISHED) {
        backgroundTask.cancel(false);
    }
    backgroundTask = null;
}

Kết quả là, sau khi doInBackground()kết thúc, đôi khi onCancelled()phương thức được gọi, và đôi khi onPostExecute(). Nhưng ít nhất thì việc chấm dứt tác vụ không đồng bộ được đảm bảo.


Giống như một điều kiện chủng tộc.
msangel

0

Tham khảo câu trả lời của Yanchenko vào ngày 29 tháng 4 năm 10: Sử dụng cách tiếp cận 'trong khi (đang chạy)' rất gọn gàng khi mã của bạn trong 'doInBackground' phải được thực thi nhiều lần trong mỗi lần thực thi AsyncTask. Nếu mã của bạn trong 'doInBackground' chỉ được thực thi một lần cho mỗi lần thực thi AsyncTask, việc gói tất cả mã của bạn trong 'doInBackground' trong một vòng lặp 'while (đang chạy)' sẽ không ngăn mã nền (chuỗi nền) chạy khi Bản thân AsyncTask bị hủy, vì điều kiện 'while (đang chạy)' sẽ chỉ được đánh giá khi tất cả mã bên trong vòng lặp while đã được thực thi ít nhất một lần. Do đó, bạn nên (a.) Chia nhỏ mã của mình trong 'doInBackground' thành nhiều khối 'trong khi (đang chạy)' hoặc (b.) Thực hiện nhiều 'isCancelt'https://developer.android.com/reference/android/os/AsyncTask.html .

Đối với tùy chọn (a.) Do đó, người ta có thể sửa đổi câu trả lời của Yanchenko như sau:

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

private volatile boolean running = true;

//...

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

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

    // does the hard work

    while (running) {
        // part 1 of the hard work
    }

    while (running) {
        // part 2 of the hard work
    }

    // ...

    while (running) {
        // part x of the hard work
    }
    return null;
}

// ...

Đối với tùy chọn (b.), Mã của bạn trong 'doInBackground' sẽ trông giống như sau:

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

//...

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

    // part 1 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // part 2 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // ...

    // part x of the hard work
    // ...
    if (isCancelled()) {return null;}
}

// ...
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.