เธรด Android AsyncTask จำกัด ไหม


95

ฉันกำลังพัฒนาแอปพลิเคชันที่ฉันต้องอัปเดตข้อมูลทุกครั้งที่ผู้ใช้เข้าสู่ระบบฉันยังใช้ฐานข้อมูลในโทรศัพท์ด้วย สำหรับการดำเนินการทั้งหมด (การอัปเดตการดึงข้อมูลจากฐานข้อมูลและอื่น ๆ ) ฉันใช้งาน async จนถึงตอนนี้ฉันไม่เห็นว่าทำไมฉันไม่ควรใช้มัน แต่เมื่อเร็ว ๆ นี้ฉันพบว่าถ้าฉันดำเนินการบางอย่างงาน async ของฉันเพียงแค่หยุดดำเนินการล่วงหน้าและอย่าข้ามไปที่ doInBackground มันแปลกเกินไปที่จะปล่อยไว้แบบนั้นดังนั้นฉันจึงพัฒนาแอปพลิเคชั่นง่ายๆอีกตัวเพื่อตรวจสอบว่ามีอะไรผิดปกติ และแปลกพอฉันได้รับพฤติกรรมเดียวกันเมื่อจำนวนงาน async ทั้งหมดถึง 5 งานที่ 6 หยุดการดำเนินการล่วงหน้า

Android มีการ จำกัด asyncTasks ใน Activity / App หรือไม่? หรือเป็นเพียงข้อผิดพลาดบางอย่างและควรรายงาน? มีใครประสบปัญหาเดียวกันและอาจพบวิธีแก้ปัญหาหรือไม่?

นี่คือรหัส:

เพียงสร้าง 5 เธรดเหล่านี้เพื่อทำงานในเบื้องหลัง:

private class LongAsync extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");
        isRunning = true;
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        while (isRunning)
        {

        }
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        Log.d("TestBug","onPostExecute");
    }
}

แล้วสร้างเธรดนี้ มันจะเข้าสู่ preExecute และแฮงค์ (มันจะไม่ไปที่ doInBackground)

private class TestBug extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");

        waiting = new ProgressDialog(TestActivity.this);
        waiting.setMessage("Loading data");
        waiting.setIndeterminate(true);
        waiting.setCancelable(true);
        waiting.show();
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        waiting.cancel();
        Log.d("TestBug","onPostExecute");
    }
}

คำตอบ:


207

AsyncTasks ทั้งหมดจะถูกควบคุมภายในโดยใช้ร่วมกัน (คงที่) ThreadPoolExecutorและLinkedBlockingQueue เมื่อคุณเรียกexecuteใช้ AsyncTask ไฟล์ThreadPoolExecutorนี้จะดำเนินการเมื่อพร้อมใช้งานในอนาคต

'ฉันพร้อมเมื่อไร' พฤติกรรมของThreadPoolExecutorถูกควบคุมโดยสองพารามิเตอร์ที่ขนาดสระว่ายน้ำหลักและขนาดสระว่ายน้ำสูงสุด หากขณะนี้มีเธรดขนาดพูลคอร์น้อยกว่าที่แอ็คทีฟและมีงานใหม่เข้ามาตัวดำเนินการจะสร้างเธรดใหม่และดำเนินการทันที หากมีเธรดขนาดพูลคอร์อย่างน้อยรันอยู่มันจะพยายามจัดคิวงานและรอจนกว่าจะมีเธรดว่าง (เช่นจนกว่างานอื่นจะเสร็จสมบูรณ์) ถ้าไม่สามารถจัดคิวงานได้ (คิวสามารถมีความจุสูงสุดได้) จะสร้างเธรดใหม่ (เธรดขนาดพูลสูงสุดสูงสุด) เพื่อให้งานรันในเธรดที่ไม่ได้ใช้งานหลักที่ไม่ได้ใช้งานจะถูกยกเลิกในที่สุด ตามพารามิเตอร์การหมดเวลาที่มีชีวิตอยู่

ก่อน Android 1.6 ขนาดพูลหลักคือ 1 และขนาดพูลสูงสุดคือ 10 ตั้งแต่ Android 1.6 ขนาดพูลหลักคือ 5 และขนาดพูลสูงสุดคือ 128 ขนาดของคิวคือ 10 ในทั้งสองกรณี ระยะหมดเวลาการรักษาชีวิตคือ 10 วินาทีก่อน 2.3 และ 1 วินาทีนับจากนั้น

เมื่อคำนึงถึงสิ่งเหล่านี้ตอนนี้ก็ชัดเจนแล้วว่าทำไมAsyncTaskพินัยกรรมจึงปรากฏขึ้นเพื่อดำเนินงาน 5/6 ของคุณ งานที่ 6 กำลังเข้าคิวจนกว่างานอื่นจะเสร็จสมบูรณ์ นี่เป็นเหตุผลที่ดีมากว่าทำไมคุณไม่ควรใช้ AsyncTasks สำหรับการดำเนินการที่ยาวนานซึ่งจะป้องกันไม่ให้ AsyncTasks อื่น ๆ ทำงานต่อไป

เพื่อความสมบูรณ์หากคุณทำแบบฝึกหัดซ้ำกับงานมากกว่า 6 งาน (เช่น 30) คุณจะเห็นว่ามากกว่า 6 ครั้งจะเข้ามาdoInBackgroundเนื่องจากคิวจะเต็มและตัวดำเนินการจะถูกผลักให้สร้างเธรดผู้ปฏิบัติงานมากขึ้น หากคุณยังคงทำงานที่ต้องใช้เวลานานคุณจะเห็นว่า 20/30 เริ่มทำงานโดยยังคงอยู่ในคิว 10 รายการ


2
"นี่เป็นเหตุผลที่ดีมากว่าทำไมคุณไม่ควรใช้ AsyncTasks สำหรับการดำเนินการที่ยาวนาน" คุณมีคำแนะนำอะไรสำหรับสถานการณ์นี้ วางไข่เธรดใหม่ด้วยตนเองหรือสร้างบริการเรียกใช้งานของคุณเอง?
user123321

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

37
โปรดทราบว่าจาก Android 3.0+ จำนวนเริ่มต้นของ AsyncTasks ที่ทำงานพร้อมกันได้ลดลงเหลือ 1 ข้อมูลเพิ่มเติม: developer.android.com/reference/android/os/…
Kieran

ว้าวขอบคุณมากสำหรับคำตอบที่ดี ในที่สุดฉันก็มีคำอธิบายว่าทำไมรหัสของฉันจึงล้มเหลวเป็นระยะ ๆ และลึกลับ
Chris Knight

@antonyt อีกหนึ่งข้อสงสัยที่ยกเลิก AsyncTasks จะนับเป็นจำนวน AsyncTasks หรือไม่? เช่นนับในcore pool sizeหรือmaximum pool size?
nmxprime

9

@antonyt มีคำตอบที่ถูกต้อง แต่ในกรณีที่คุณกำลังมองหาวิธีง่ายๆคุณสามารถตรวจสอบ Needle

ด้วยวิธีนี้คุณสามารถกำหนดขนาดเธรดพูลที่กำหนดเองได้และแตกต่างจากที่AsyncTaskทำงานบนAndroid ทุกเวอร์ชันเหมือนกัน ด้วยวิธีนี้คุณสามารถพูดสิ่งต่างๆเช่น:

Needle.onBackgroundThread().withThreadPoolSize(3).execute(new UiRelatedTask<Integer>() {
   @Override
   protected Integer doWork() {
       int result = 1+2;
       return result;
   }

   @Override
   protected void thenDoUiRelatedWork(Integer result) {
       mSomeTextView.setText("result: " + result);
   }
});

หรือสิ่งที่ชอบ

Needle.onMainThread().execute(new Runnable() {
   @Override
   public void run() {
       // e.g. change one of the views
   }
}); 

มันสามารถทำอะไรได้อีกมากมาย ตรวจสอบมันออกมาบนGitHub


การกระทำครั้งสุดท้ายเมื่อ 5 ปีที่แล้ว :(
LukaszTaraszka

5

อัปเดต : ตั้งแต่ API 19 ขนาดพูลเธรดหลักถูกเปลี่ยนเพื่อให้สอดคล้องกับจำนวนซีพียูบนอุปกรณ์โดยมีขั้นต่ำ 2 และสูงสุด 4 เมื่อเริ่มต้นในขณะที่เพิ่ม CPU สูงสุด * 2 +1 - การอ้างอิง

// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

โปรดทราบว่าในขณะที่ตัวดำเนินการเริ่มต้นของ AsyncTask เป็นแบบอนุกรม (ดำเนินการทีละงานและตามลำดับที่มาถึง) ด้วยวิธีการ

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params)

คุณสามารถให้ Executor ทำงานของคุณได้ คุณอาจจัดเตรียม THREAD_POOL_EXECUTOR ให้กับตัวดำเนินการภายใต้ประทุน แต่ไม่มีการจัดลำดับของงานหรือคุณสามารถสร้าง Executor ของคุณเองและระบุได้ที่นี่ อย่างไรก็ตามโปรดสังเกตคำเตือนใน Javadocs อย่างระมัดระวัง

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

อีกสิ่งหนึ่งที่ควรทราบก็คือทั้งเฟรมเวิร์กที่มีให้ Executors THREAD_POOL_EXECUTOR และเวอร์ชันซีเรียล SERIAL_EXECUTOR (ซึ่งเป็นค่าเริ่มต้นสำหรับ AsyncTask) เป็นแบบคงที่ (โครงสร้างระดับคลาส) และด้วยเหตุนี้จึงแชร์ในทุกอินสแตนซ์ของ AsyncTask ในกระบวนการแอปของคุณ

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