วิธีที่ดีที่สุดในการยกเลิกการรัน AsyncTask


108

ฉันใช้งานการดึงไฟล์เสียงระยะไกลและการดำเนินการเล่นไฟล์เสียงในเธรดพื้นหลังโดยใช้AsyncTask. Cancellableแถบความคืบหน้าจะแสดงเวลาที่สามารถดึงข้อมูลการดำเนินงานวิ่ง

ฉันต้องการยกเลิก / ยกเลิกการAsyncTaskทำงานเมื่อผู้ใช้ยกเลิก (ตัดสินใจต่อต้าน) การดำเนินการ วิธีที่ดีที่สุดในการจัดการกรณีดังกล่าวคืออะไร?

คำตอบ:


76

เพิ่งค้นพบว่าAlertDialogsเป็นboolean cancel(...);ฉันใช้ทุกที่จริง ๆ แล้วไม่ทำอะไรเลย เยี่ยมมาก
ดังนั้น...

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
แทนที่จะสร้างบูลีนแฟล็กสำหรับการทำงานคุณไม่สามารถลบออกและทำสิ่งนี้ได้ในขณะนี้ (! isCanceled ()) ???
ขงจื๊อ

36
จากเอกสารเกี่ยวกับ onCancelled (): "รันบนเธรด UI หลังจากยกเลิก (บูลีน) ถูกเรียกและ doInBackground (Object []) เสร็จสิ้น" 'หลัง' หมายความว่าการตั้งค่าสถานะใน onCancelled และการตรวจสอบใน doInBackground ไม่มีเหตุผล
lopek

2
@confucius ถูกต้อง แต่วิธีนี้เธรดพื้นหลังจะไม่ถูกขัดจังหวะสมมติว่าในกรณีที่อัปโหลดภาพกระบวนการอัปโหลดจะดำเนินต่อไปในพื้นหลังและเราไม่ได้รับการเรียก onPostExecute
umesh

1
@ DanHulme ฉันเชื่อว่าฉันอ้างถึงข้อมูลโค้ดที่ให้ไว้ในคำตอบไม่ใช่ความคิดเห็นของขงจื้อ (ซึ่งถูกต้อง)
lopek

4
ใช่คำตอบนี้ไม่ได้ทำงาน ใน doInBackground ให้แทนที่while(running)ด้วยwhile(!isCancelled())ตามที่คนอื่นพูดไว้ที่นี่ในความคิดเห็น
matt5784

76

หากคุณกำลังคำนวณ :

  • คุณต้องตรวจสอบisCancelled()เป็นระยะ

หากคุณกำลังส่งคำขอ HTTP :

  • บันทึกอินสแตนซ์ของคุณHttpGetหรือHttpPostที่ไหนสักแห่ง (เช่นฟิลด์สาธารณะ)
  • หลังจากcancelโทรrequest.abort(). นี้จะทำให้ถูกโยนภายในของคุณIOExceptiondoInBackground

ในกรณีของฉันฉันมีคลาสตัวเชื่อมต่อที่ฉันใช้ใน AsyncTasks ต่างๆ เพื่อให้มันง่ายฉันเพิ่มใหม่วิธีการเรียนที่และเรียกวิธีการนี้โดยตรงหลังจากโทรabortAllRequestscancel


ขอบคุณมันใช้งานได้ แต่จะหลีกเลี่ยงข้อยกเว้นในกรณีนี้ได้อย่างไร?
begiPass

คุณต้องโทรHttpGet.abort()จากเธรดพื้นหลังมิฉะนั้นคุณจะได้รับไฟล์android.os.NetworkOnMainThreadException.
Heath Borders

@wrygiel หากคุณกำลังส่งคำขอ HTTP ไม่ควรcancel(true)ขัดจังหวะคำขอใช่หรือไม่ จากเอกสาร: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

ถ้าคุณมีซีพียูการดำเนินการบริโภคใน AsyncTask cancel(true)ดังนั้นคุณต้องโทร ฉันใช้มันและได้ผล
SMMousavi

20

สิ่งนี้ก็คือ AsyncTask.cancel () เรียกเฉพาะฟังก์ชัน onCancel ในงานของคุณ นี่คือที่ที่คุณต้องการจัดการกับคำขอยกเลิก

นี่คืองานเล็ก ๆ ที่ฉันใช้เพื่อเรียกใช้วิธีการอัปเดต

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
สิ่งนี้จะใช้งานได้ แต่มีเหตุผลเมื่อคุณกำลังรอการตอบกลับของเซิร์ฟเวอร์และคุณเพิ่งดำเนินการ db ดังนั้นควรแสดงถึงการเปลี่ยนแปลงที่ถูกต้องในกิจกรรมของคุณ ฉันเขียนบล็อกเกี่ยวกับเรื่องนั้นดูคำตอบของฉัน
Vikas

4
ดังที่กล่าวไว้ในความคิดเห็นเกี่ยวกับคำตอบที่ยอมรับคุณไม่จำเป็นต้องสร้างrunningธงของคุณเอง AsyncTask มีแฟล็กภายในที่ตั้งค่าเมื่องานถูกยกเลิก แทนที่while (running)ด้วยwhile (!isCancelled()). developer.android.com/reference/android/os/… ดังนั้นในกรณีง่ายๆนี้คุณไม่จำเป็นต้องonCancelled()แทนที่
ToolmakerSteve

11

ง่ายๆ: อย่าใช้AsyncTaskไฟล์. AsyncTaskได้รับการออกแบบมาสำหรับการใช้งานสั้น ๆ ที่จบลงอย่างรวดเร็ว (หลายสิบวินาที) ดังนั้นจึงไม่จำเป็นต้องยกเลิก "การเล่นไฟล์เสียง" ไม่มีคุณสมบัติ คุณไม่จำเป็นต้องมีเธรดพื้นหลังสำหรับการเล่นไฟล์เสียงธรรมดา


คุณแนะนำให้เราใช้เธรด java ปกติและ "ยกเลิก" การรันเธรดโดยใช้ตัวแปรบูลีนระเหย - วิธี Java แบบเดิมหรือไม่?
Samuh

34
ไมค์ไม่มีความผิด แต่นั่นไม่ใช่คำตอบที่ยอมรับได้ AsyncTask มีวิธีการยกเลิกและควรใช้งานได้ เท่าที่ฉันบอกได้มันไม่ได้ - แต่แม้ว่าฉันจะทำผิด แต่ก็ควรมีวิธีที่ถูกต้องในการยกเลิกงาน วิธีนี้จะไม่มีอยู่จริง และแม้แต่งานสั้น ๆ ก็อาจต้องยกเลิก - ฉันมีกิจกรรมที่จะเริ่ม AsyncTask ทันทีที่โหลดและถ้าผู้ใช้กลับมาทันทีหลังจากเปิดงานพวกเขาจะเห็น Force Close ในวินาทีต่อมาเมื่องานเสร็จสิ้น แต่ไม่มีบริบท มีอยู่เพื่อใช้ใน onPostExecute
Eric Mill

10
@Klondike: ไม่รู้ว่า "ไมค์" คือใคร "แต่นั่นไม่ใช่คำตอบที่ยอมรับได้" - ยินดีรับฟังความคิดเห็นของคุณ "AsyncTask มีวิธีการยกเลิกและควรใช้งานได้" - การยกเลิกเธรดใน Java เป็นปัญหามาประมาณ 15 ปีแล้ว ไม่มีอะไรเกี่ยวข้องกับ Android มากนัก สำหรับสถานการณ์ "บังคับปิด" ของคุณซึ่งสามารถแก้ไขได้โดยตัวแปรบูลีนซึ่งคุณทดสอบonPostExecute()เพื่อดูว่าคุณควรดำเนินการต่อไปหรือไม่
CommonsWare

1
@Tejaswi Yerukalapudi: ยิ่งไปกว่านั้นมันจะไม่ทำอะไรโดยอัตโนมัติ ดูคำตอบที่ยอมรับสำหรับคำถามนี้
CommonsWare

10
คุณควรตรวจสอบเมธอด isCancelled เป็นระยะใน doInBackground ของคุณบน AsyncTask มันอยู่ในเอกสาร: developer.android.com/reference/android/os/…
Christopher Perry

4

วิธีเดียวที่จะทำได้คือตรวจสอบค่าของเมธอด isCancelled () และหยุดการเล่นเมื่อกลับเป็นจริง


4

นี่คือวิธีที่ฉันเขียน AsyncTask ของฉัน
ประเด็นสำคัญคือเพิ่ม 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
ฉันพบว่าการเรียกยกเลิก (true) บนงาน async และการตรวจสอบ isCancelled () เป็นระยะ ๆ จะได้ผล แต่ขึ้นอยู่กับว่างานของคุณกำลังทำอะไรอยู่อาจใช้เวลาถึง 60 วินาทีก่อนที่จะถูกแทรกแซง การเพิ่ม Thread.sleep (1) ช่วยให้สามารถแทรกสอดได้ทันที (งาน Async เข้าสู่สถานะรอ แต่จะไม่ถูกทิ้งทันที) ขอบคุณสำหรับสิ่งนี้.
John J Smith

0

ตัวแปรคลาส AsyncTask ส่วนกลางของเรา

LongOperation LongOperationOdeme = new LongOperation();

และการดำเนินการ KEYCODE_BACK ซึ่งขัดจังหวะ AsyncTask

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

มันใช้ได้กับฉัน


0

ฉันไม่ชอบที่จะบังคับให้ขัดจังหวะงาน async ของฉันโดยcancel(true)ไม่จำเป็นเพราะอาจมีทรัพยากรที่ต้องปลดปล่อยเช่นการปิดซ็อกเก็ตหรือสตรีมไฟล์การเขียนข้อมูลไปยังฐานข้อมูลภายในเครื่องเป็นต้นในทางกลับกันฉันต้องเผชิญกับสถานการณ์ที่ งาน async ปฏิเสธที่จะทำบางส่วนให้เสร็จสิ้นเช่นบางครั้งเมื่อปิดกิจกรรมหลักและฉันขอให้งาน async เสร็จสิ้นจากภายในonPause()วิธีการของกิจกรรม running = falseดังนั้นจึงไม่เพียงเรื่องของการโทร ฉันต้องไปหาวิธีแก้ปัญหาแบบผสม: ทั้งสองโทรrunning = falseจากนั้นให้งาน async ไม่กี่มิลลิวินาทีเพื่อให้เสร็จสิ้นแล้วจึงโทรcancel(false)หรือ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;
}

เป็นผลข้างเคียงหลังจากdoInBackground()เสร็จสิ้นบางครั้งวิธีการที่เรียกว่าและบางครั้งonCancelled() onPostExecute()แต่อย่างน้อยก็รับประกันการยุติงาน async


ดูสภาพการแข่งขัน
msangel

0

อ้างอิงถึงคำตอบของ Yanchenko ในวันที่ 29 เมษายน 2553: การใช้วิธีการ 'while (running)' เป็นไปอย่างเรียบร้อยเมื่อโค้ดของคุณภายใต้ "doInBackground" ต้องดำเนินการหลายครั้งในระหว่างการดำเนินการ AsyncTask ทุกครั้ง หากโค้ดของคุณภายใต้ 'doInBackground' ต้องดำเนินการเพียงครั้งเดียวต่อการเรียกใช้ AsyncTask การตัดโค้ดทั้งหมดของคุณภายใต้ 'doInBackground' ในลูป 'while (running)' จะไม่หยุดการทำงานของโค้ดพื้นหลัง (เธรดพื้นหลัง) เมื่อ AsyncTask นั้นถูกยกเลิกเนื่องจากเงื่อนไข 'while (running)' จะได้รับการประเมินก็ต่อเมื่อโค้ดทั้งหมดภายในลูป while ถูกเรียกใช้งานอย่างน้อยหนึ่งครั้ง ดังนั้นคุณควร (a.) แบ่งโค้ดของคุณภายใต้ 'doInBackground' เป็นหลาย ๆ 'while (running)' blocks หรือ (b.) ทำการ 'isCancelled' จำนวนมากhttps://developer.android.com/reference/android/os/AsyncTask.html

สำหรับตัวเลือก (a.) คุณสามารถแก้ไขคำตอบของ Yanchenko ได้ดังนี้:

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

// ...

สำหรับตัวเลือก (b.) โค้ดของคุณใน 'doInBackground' จะมีลักษณะดังนี้:

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

// ...
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.