ดำเนินการ AsyncTask หลาย ๆ ครั้ง


127

ในกิจกรรมของฉันฉันใช้คลาสที่ขยายจาก AsyncTask และพารามิเตอร์ซึ่งเป็นอินสแตนซ์ของ AsyncTask นั้น เมื่อฉันโทรไปmInstanceOfAT.execute("")ทุกอย่างเรียบร้อยดี แต่แอพขัดข้องเมื่อฉันกดปุ่มอัปเดตซึ่งเรียก AsyncTask อีกครั้ง (ในกรณีที่งานเครือข่ายไม่ทำงาน) สาเหตุจะปรากฏข้อยกเว้นซึ่งระบุว่า

ไม่สามารถเรียกใช้งานได้: งานได้ถูกดำเนินการไปแล้ว (สามารถดำเนินการได้เพียงครั้งเดียว)

ฉันได้ลองโทรยกเลิก (true) สำหรับอินสแตนซ์ของ Asyctask แล้ว แต่ก็ใช้ไม่ได้เช่นกัน ทางออกเดียวคือการสร้างอินสแตนซ์ใหม่ของ Asyntask เป็นวิธีที่ถูกต้องหรือไม่?

ขอบคุณ

คำตอบ:


217

AsyncTask สามารถใช้อินสแตนซ์ได้เพียงครั้งเดียว

ให้เรียกงานของคุณว่า new MyAsyncTask().execute("");

จากเอกสาร AsyncTask API:

กฎการเธรด

มีกฎของเธรดสองสามข้อที่ต้องปฏิบัติตามเพื่อให้คลาสนี้ทำงานได้อย่างถูกต้อง:

  • ต้องสร้างอินสแตนซ์งานบนเธรด UI
  • ต้องเรียกใช้ (Params ... ) บนเธรด UI
  • อย่าเรียกใช้ onPreExecute (), onPostExecute (Result), doInBackground (Params ... ), onProgressUpdate (Progress ... ) ด้วยตนเอง
  • งานสามารถดำเนินการได้เพียงครั้งเดียว (ข้อยกเว้นจะถูกยกเลิกหากมีการพยายามดำเนินการครั้งที่สอง)

2
สิ่งที่ฉันบอกว่าฉันทำไปแล้วนั้นเป็นไปได้เพียงอย่างเดียวหรือไม่? สาเหตุที่ฉันต้องการบันทึก memmory แทนที่จะสร้างวัตถุใหม่
Dayerman


@StevePrentice: ถ้าฉันสร้างอินสแตนซ์ของงานทุกๆ x วินาทีด้วยภารกิจใหม่ () เรียกใช้งาน (พารามิเตอร์) เพื่อส่งข้อมูลไปยังเซิร์ฟเวอร์ตัวรวบรวมขยะจะปล่อยหน่วยความจำได้อย่างไรเมื่อดำเนินการเสร็จสิ้น
Ant4res

3
@ Ant4res ตราบใดที่คุณไม่ได้อ้างถึงอินสแตนซ์งาน async GC จะปล่อยหน่วยความจำ อย่างไรก็ตามหากคุณมีงานเบื้องหลังที่กำลังดำเนินอยู่คุณสามารถลองทำแบบวนซ้ำภายใน doInBackground แล้วโทรไปที่ PublishProgress เพื่ออัพเดตความคืบหน้า หรืออีกวิธีหนึ่งคือการใส่งานของคุณลงในเธรดพื้นหลัง มีแนวทางต่างๆมากมายที่นี่ แต่ไม่สามารถแนะนำกันได้หากไม่มีรายละเอียด
Steve Prentice

28

เหตุผลของ ASyncTask แบบ fire-and-forget มีรายละเอียดค่อนข้างดีในคำตอบของ Steve Prentice - อย่างไรก็ตามในขณะที่คุณถูก จำกัดจำนวนครั้งที่คุณดำเนินการ ASyncTask คุณมีอิสระที่จะทำสิ่งที่คุณต้องการในขณะที่เธรดกำลังทำงานอยู่ .. .

ใส่รหัสปฏิบัติการของคุณภายในลูปภายในdoInBackground ()และใช้การล็อกพร้อมกันเพื่อทริกเกอร์การดำเนินการแต่ละครั้ง คุณสามารถเรียกผลโดยใช้publishProgress () / onProgressUpdate ()

ตัวอย่าง:

class GetDataFromServerTask extends AsyncTask<Input, Result, Void> {

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition tryAgain = lock.newCondition();
    private volatile boolean finished = false;

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

        lock.lockInterruptibly();

        do { 
            // This is the bulk of our task, request the data, and put in "result"
            Result result = ....

            // Return it to the activity thread using publishProgress()
            publishProgress(result);

            // At the end, we acquire a lock that will delay
            // the next execution until runAgain() is called..
            tryAgain.await();

        } while(!finished);

        lock.unlock();
    }

    @Override
    protected void onProgressUpdate(Result... result) 
    {
        // Treat this like onPostExecute(), do something with result

        // This is an example...
        if (result != whatWeWant && userWantsToTryAgain()) {
            runAgain();
        }
    }

    public void runAgain() {
        // Call this to request data from the server again
        tryAgain.signal();
    }

    public void terminateTask() {
        // The task will only finish when we call this method
        finished = true;
        lock.unlock();
    }

    @Override
    protected void onCancelled() {
        // Make sure we clean up if the task is killed
        terminateTask();
    }
}

แน่นอนว่าสิ่งนี้ซับซ้อนกว่าการใช้งาน ASyncTask แบบเดิมเล็กน้อยและคุณเลิกใช้งานPublishProgress ()สำหรับการรายงานความคืบหน้าจริง แต่ถ้าคุณกังวลเกี่ยวกับหน่วยความจำวิธีนี้จะช่วยให้มั่นใจได้ว่า ASyncTask เพียงตัวเดียวยังคงอยู่ในฮีปขณะรันไทม์


แต่ประเด็นก็คือฉันไม่ต้องการดำเนินการ Asyntask ซ้ำในขณะที่กำลังทำงานอยู่ แต่เนื่องจากอันนี้เสร็จสิ้นและไม่ได้รับข้อมูลเท่าที่ควรจากนั้นเรียกอีกครั้ง
เดย์แมน

อันที่จริงคุณดำเนินการ ASyncTask เพียงครั้งเดียวด้วยวิธีนี้และคุณสามารถตรวจสอบว่าข้อมูลถูกต้องภายในเมธอด onPublishProgress หรือไม่ (หรือมอบหมายการตรวจสอบที่อื่น) ฉันใช้รูปแบบนี้สำหรับปัญหาที่คล้ายกันในขณะที่ย้อนกลับไป (งานจำนวนมากถูกยิงติดต่อกันอย่างรวดเร็วเสี่ยงต่อขนาดฮีป)
seanhodges

แต่ถ้าในขณะนั้นเซิร์ฟเวอร์ไม่ตอบสนองและฉันต้องการลองอีกครั้งในอีก 10 วินาทีในภายหลัง AsyncTask เสร็จสิ้นแล้วหรือยังงั้นฉันต้องเรียกมันอีกครั้ง
Dayerman

ฉันได้เพิ่มโค้ดตัวอย่างเพื่ออธิบายความหมาย ASyncTask จะเสร็จสิ้นก็ต่อเมื่อคุณพอใจกับผลลัพธ์แล้วเรียก "terminateTask ()"
seanhodges

1
ถ้าคุณได้รับIllegalMonitorStateExceptionในrunAgain(เรียกโดยonProgressUpdate) ดูคำตอบนี้: stackoverflow.com/a/42646476/2711811 มันแสดงให้เห็น (และทำงานให้ฉัน) ที่signal()ตอบสนองความต้องการที่จะล้อมรอบด้วย/lock unlockนี้อาจจะต้องทำอย่างไรกับระยะเวลาของการโทรpublishProgress onProgressUpdate
Andy

2

ฉันมีปัญหาเดียวกัน ในกรณีของฉันฉันมีงานที่ฉันต้องการทำในonCreate()และในonResume() ดังนั้นฉันจึงทำให้ Asynctask เป็นแบบคงที่และรับอินสแตนซ์จากมัน ตอนนี้เรายังมีปัญหาเดิม

สิ่งที่ฉันทำใน onPostExecute () คือ:

instance = null;

โปรดทราบว่าฉันตรวจสอบในเมธอด getInstance แบบคงที่ว่าอินสแตนซ์ของฉันไม่เป็นโมฆะฉันจะสร้างมันขึ้นมา

if (instance == null){
    instance = new Task();
}
return instance;

วิธีการใน postExecute จะทำให้อินสแตนซ์ว่างเปล่าและสร้างใหม่ แน่นอนสิ่งนี้สามารถทำได้นอกชั้นเรียน


1

ฉันทำให้งานหมุนเวียนของฉันเป็นแบบคงที่ซึ่งจะช่วยให้ฉันแนบถอดและแนบกลับเข้ากับเธรด UI ในการเปลี่ยนแปลงการหมุน แต่กลับไปที่คำถามของคุณสิ่งที่ฉันทำคือสร้างแฟล็กเพื่อดูว่าเธรดกำลังทำงานอยู่หรือไม่ เมื่อคุณต้องการรีสตาร์ทเธรดฉันตรวจสอบว่างานการหมุนกำลังทำงานอยู่หรือไม่หากฉันกำลังเตือน ถ้าไม่เป็นเช่นนั้นฉันจะทำให้เป็นโมฆะแล้วสร้างใหม่ซึ่งจะแก้ไขข้อผิดพลาดที่คุณเห็น นอกจากนี้เมื่อทำสำเร็จฉันจะยกเลิกงานรับรู้การหมุนที่เสร็จสมบูรณ์เพื่อให้พร้อมที่จะดำเนินการอีกครั้ง


0

ใช่มันเป็นความจริงเอกสารบอกว่าสามารถดำเนินการ Asyntask ได้เพียงคนเดียว

ทุกครั้งที่คุณต้องใช้คุณจะต้องเช่น:

// Any time if you need to call her
final FirmwareDownload fDownload = new FirmwareDownload();
fDownload.execute("your parameter");

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