เรียกใช้ AsyncTasks หลาย ๆ ตัวในเวลาเดียวกัน - เป็นไปไม่ได้ใช่ไหม?


259

ฉันพยายามเรียกใช้ AsyncTasks สองรายการในเวลาเดียวกัน (แพลตฟอร์มคือ Android 1.5, HTC Hero) อย่างไรก็ตามมีเพียงคนแรกที่ถูกประหารชีวิต ต่อไปนี้เป็นตัวอย่างข้อมูลสั้น ๆ เพื่ออธิบายปัญหาของฉัน:

public class AndroidJunk extends Activity {
 class PrinterTask extends AsyncTask<String, Void, Void> {
     protected Void doInBackground(String ... x) {
      while (true) {
       System.out.println(x[0]);
       try {
        Thread.sleep(1000);
       } catch (InterruptedException ie) {
        ie.printStackTrace();
       }
      }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new PrinterTask().execute("bar bar bar");
        new PrinterTask().execute("foo foo foo");

        System.out.println("onCreate() is done.");
    }
}

ผลลัพธ์ที่ฉันคาดหวังคือ:

onCreate() is done.
bar bar bar
foo foo foo
bar bar bar
foo foo foo

และอื่น ๆ อย่างไรก็ตามสิ่งที่ฉันได้รับคือ:

onCreate() is done.
bar bar bar
bar bar bar
bar bar bar

AsyncTask ที่สองไม่เคยถูกดำเนินการ หากฉันเปลี่ยนลำดับของคำสั่ง execute () เฉพาะงาน foo เท่านั้นที่จะสร้างเอาต์พุต

ฉันขาดสิ่งที่ชัดเจนที่นี่และ / หรือทำอะไรที่โง่? ไม่สามารถเรียกใช้ AsyncTasks สองรายการพร้อมกันได้หรือไม่

แก้ไข: ฉันรู้ว่าโทรศัพท์ที่เป็นปัญหารัน Android 1.5 ฉันอัปเดตปัญหา descr ตาม ฉันไม่มีปัญหากับ HTC Hero ที่ใช้ Android 2.1 อืม ...


รหัสของคุณเหมาะกับฉันดังนั้นปัญหาต้องอยู่ที่อื่น คุณป้อนตัวกรองในมุมมอง LogCat ของคุณหรือไม่ ;-)
mreichelt

หืมนั่นแปลก ฉันไม่มีตัวกรองใน logcat คุณใช้ 1.6 ด้วยหรือไม่ ถ้าเป็นเช่นนั้นโทรศัพท์รุ่นไหน?
rodion

อุ๊ปส์เพิ่งรู้ว่ามันใช้งาน (เก่า) Android 1.5
rodion

1
ฉันใช้ Android 1.6 เป็นเป้าหมายและตัวจำลอง Android 2.1 ดังนั้นหากปัญหาเกิดขึ้นกับ HTC Hero ที่ใช้ Android 1.5 เท่านั้น - ไขปัญหาเหล่านั้นได้ ;-) HTC Hero ได้อัปเดตเป็น Android เวอร์ชันที่ใหม่กว่าอยู่แล้ว ฉันจะไม่สนใจเรื่องนี้หากมีผู้ผลิตบางรายที่ขันสิ่งต่างๆ นอกจากนี้ฉันจะไม่สนใจเกี่ยวกับ Android 1.5 อีกต่อไป
mreichelt

AsyncTask ควรใช้สำหรับงานที่มีระยะเวลาสั้นกว่า 5 ms ย้ายไปที่ ThreadPoolExecutor ( developer.android.com/reference/java/util/concurrent/… ) โพสต์ที่เกี่ยวข้อง: stackoverflow.com/questions/6964011/…
Ravindra babu

คำตอบ:


438

AsyncTask ใช้รูปแบบกลุ่มเธรดสำหรับการเรียกใช้เนื้อหาจาก doInBackground () ปัญหาคือเริ่มต้น (ในรุ่น Android OS ต้น) ขนาดพูลเป็นเพียง 1 ซึ่งหมายถึงไม่มีการคำนวณแบบขนานสำหรับ AsyncTasks แต่ต่อมาพวกเขาก็แก้ไขและตอนนี้ขนาดคือ 5 ดังนั้นอย่างมากที่สุด 5 AsyncTasks สามารถทำงานพร้อมกัน น่าเสียดายที่ฉันจำไม่ได้ว่าพวกเขาเปลี่ยนรุ่นอะไร

UPDATE:

นี่คือสิ่งที่ปัจจุบัน (2012-01-27) API พูดเกี่ยวกับสิ่งนี้:

เมื่อเปิดตัวครั้งแรก AsyncTasks จะถูกดำเนินการตามลำดับบนเธรดพื้นหลังเดียว เริ่มต้นด้วย DONUT สิ่งนี้ถูกเปลี่ยนเป็นกลุ่มของเธรดที่อนุญาตให้งานหลายอย่างทำงานพร้อมกัน หลังจาก HONEYCOMB มีการวางแผนที่จะเปลี่ยนกลับไปเป็นเธรดเดี่ยวเพื่อหลีกเลี่ยงข้อผิดพลาดทั่วไปของแอปพลิเคชันที่เกิดจากการเรียกใช้แบบขนาน หากคุณต้องการการประมวลผลแบบขนานอย่างแท้จริงคุณสามารถใช้รุ่น executeOnExecutor (Executor, Params ... ) ของวิธีนี้ด้วย THREAD_POOL_EXECUTOR อย่างไรก็ตามดูความเห็นที่นั่นสำหรับคำเตือนในการใช้งาน

โดนัทเป็น Android 1.6, HONEYCOMB เป็น Android 3.0

อัปเดต: 2

ดูความคิดเห็นโดยจากkabukoMar 7 2012 at 1:27

ปรากฎว่าสำหรับ APIs ที่ใช้ "กลุ่มของเธรดที่อนุญาตให้ใช้งานหลายงานพร้อมกัน" (เริ่มจาก 1.6 และสิ้นสุดที่ 3.0) จำนวนการทำงานพร้อมกันของ AsyncTasks ขึ้นอยู่กับจำนวนงานที่ผ่านการดำเนินการแล้ว แต่ ยังไม่เสร็จสิ้นของพวกเขาdoInBackground()เลย

นี่คือการทดสอบ / ยืนยันโดยฉันใน 2.2 สมมติว่าคุณมี AsyncTask doInBackground()ที่กำหนดเองที่เพิ่งหลับเป็นครั้งที่สองใน AsyncTasks ใช้คิวขนาดคงที่ภายในเพื่อจัดเก็บงานที่ล่าช้า ขนาดคิวคือ 10 ตามค่าเริ่มต้น หากคุณเริ่มงานที่กำหนดเอง 15 รายการของคุณในแถวจากนั้น 5 อันดับแรกจะเข้าสู่งานของพวกเขาdoInBackground()แต่ส่วนที่เหลือจะรอในคิวของเธรดผู้ทำงานอิสระ ทันทีที่หนึ่งใน 5 ครั้งแรกเสร็จสิ้นดังนั้นจึงปล่อยเธรดผู้ปฏิบัติงานงานจากคิวจะเริ่มดำเนินการ ดังนั้นในกรณีนี้มากที่สุด 5 งานจะทำงานพร้อมกัน อย่างไรก็ตามถ้าคุณเริ่มงานที่กำหนดเอง 16 รายการของคุณในแถวจากนั้น 5 อันดับแรกจะเข้าสู่งานของพวกเขาdoInBackground()ส่วนที่เหลือ 10 จะเข้าสู่คิว แต่สำหรับวันที่ 16 จะมีการสร้างเธรดคนงานใหม่เพื่อที่จะเริ่มดำเนินการทันที ดังนั้นในกรณีนี้ที่มากที่สุด 6 งานจะทำงานพร้อมกัน

มีการ จำกัด จำนวนงานที่สามารถเรียกใช้พร้อมกันได้ ตั้งแต่AsyncTaskใช้ปฏิบัติการสระว่ายน้ำหัวข้อที่มี จำกัด จำนวนสูงสุดของหัวข้อการปฏิบัติงาน (128) และงานล่าช้าคิวได้กำหนดขนาด 10 ถ้าคุณพยายามที่จะดำเนินการมากกว่า 138 งานของคุณเอง app java.util.concurrent.RejectedExecutionExceptionที่จะผิดพลาดด้วย

เริ่มต้นที่ 3.0 API จะช่วยให้คุณใช้ตัวจัดการเธรดพูลที่กำหนดเองของคุณผ่านAsyncTask.executeOnExecutor(Executor exec, Params... params)วิธีการ สิ่งนี้อนุญาตให้กำหนดค่าขนาดของคิวงานที่ล่าช้าถ้าค่าดีฟอลต์ 10 ไม่ใช่สิ่งที่คุณต้องการ

ตามที่ @Knossos กล่าวถึงมีตัวเลือกให้ใช้AsyncTaskCompat.executeParallel(task, params);จากไลบรารีสนับสนุน v.4 เพื่อเรียกใช้งานแบบขนานโดยไม่ต้องรบกวนกับระดับ API วิธีการนี้เลิกใช้แล้วในระดับ API 26.0.0

อัปเดต: 3

นี่คือแอปทดสอบอย่างง่ายสำหรับเล่นกับจำนวนของงานการดำเนินการแบบอนุกรมและแบบขนาน: https://github.com/vitkhudenko/test_asynctask

อัปเดต: 4 (ขอบคุณ @penkzhou สำหรับการชี้เรื่องนี้)

เริ่มต้นจาก Android 4.4 AsyncTaskทำงานแตกต่างจากสิ่งที่ได้อธิบายไว้ในUPDATE: 2ส่วน มีการแก้ไขเพื่อป้องกันไม่ให้AsyncTaskสร้างกระทู้มากเกินไป

ก่อน Android 4.4 (API 19) AsyncTaskมีฟิลด์ต่อไปนี้:

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(10);

ใน Android 4.4 (API 19) ฟิลด์ด้านบนจะเปลี่ยนเป็น:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

การเปลี่ยนแปลงนี้จะเพิ่มขนาดของคิวเป็น 128 รายการและลดจำนวนเธรดสูงสุดเป็นจำนวนแกน CPU * 2 + 1 แอปยังคงสามารถส่งงานจำนวนเดียวกันได้


11
เพียงแค่ FYI ขนาดพูลหลักคือ 5 แต่สูงสุดคือ 128 ซึ่งหมายความว่าหากคุณเติมคิวของคุณมากกว่า 5 (มากถึง 128) จะสามารถทำงานพร้อมกันได้
kabuko

2
@kabuko: ถูกต้องแน่นอน เพียงแค่ตรวจสอบเรื่องนี้ใน 2.2 และยืนยันว่าหากมี 5 งานที่กำลังทำงานอยู่ (ขนาดพูลหลักคือ 5) จากนั้นก็จะเริ่มใส่ส่วนที่เหลือของงานลงในคิวของงานล่าช้า แต่ถ้าคิวเต็มแล้ว (ขนาดคิวสูงสุดคือ 10 ) จากนั้นจะเริ่มเธรดผู้ทำงานเพิ่มเติมสำหรับงานเพื่อให้งานเริ่มต้นทันที
Vit Khudenko

1
เพิ่งบันทึกวันของฉันฮีโร่คำบรรยายภาพ :) ขอบคุณ
Karoly

2
โปรดอัปเดตคำตอบนี้สำหรับฟังก์ชั่นรองรับใหม่ ตัวอย่าง:AsyncTaskCompat.executeParallel(task, params);
Knossos

1
@Arhimed โปรดอัปเดตคำตอบตั้งแต่AsyncTaskCompat.executeParallel(task, params);ตอนนี้เลิกใช้แล้ว
Kirill Starostin

218

วิธีนี้ช่วยให้สามารถประมวลผลแบบขนานบน Android ทุกรุ่นที่มี API 4+ (Android 1.6+):

@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
void startMyTask(AsyncTask asyncTask) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}

นี่เป็นบทสรุปของคำตอบที่ยอดเยี่ยมของ Arhimed

โปรดตรวจสอบให้แน่ใจว่าคุณใช้ API ระดับ 11 หรือสูงกว่าเป็นเป้าหมายการสร้างโครงการของคุณ ใน Eclipse, Project > Properties > Android > Project Build Targetที่อยู่ สิ่งนี้จะไม่หยุดความเข้ากันได้แบบย้อนหลังกับระดับ API ที่ต่ำกว่า minSdkVersionไม่ต้องกังวลว่าคุณจะได้รับข้อผิดพลาดผ้าสำลีถ้าใช้โดยไม่ได้ตั้งใจของคุณคุณสมบัติแนะนำช้ากว่า หากคุณต้องการใช้คุณสมบัติที่แนะนำในภายหลังminSdkVersionคุณสามารถระงับข้อผิดพลาดเหล่านั้นได้โดยใช้คำอธิบายประกอบ แต่ในกรณีนี้คุณต้องดูแลความเข้ากันได้ของตัวคุณเองด้วย นี่คือสิ่งที่เกิดขึ้นในข้อมูลโค้ดด้านบน


3
ต้องเปลี่ยน TimerTask.THREAD_POOL_EXECUTOR เป็น AsynTask.THREAD_POOL_EXECUTOR แล้วตัวอย่างนี้ใช้งานได้
Ronnie

2
หากต้องการโมฆะปัญหาคุณสามารถโทรทั่วไป: void startMyTask(AsyncTask<Void, ?, ?> asyncTask) (ถ้า doInBackground ของคุณใช้โมฆะ)
Peter

คุณสามารถลดความซับซ้อนของรหัสข้างต้นได้มากขึ้นถ้าคลาสของคุณขยาย AsyncTask <Void, Integer, Void> ขอบคุณสุลต่าน
Peter Arandorenko

1
ขอบคุณมาก. ฉันมีสถานการณ์ที่ฉันต้องทำงาน async สองงานพร้อมกันเพื่ออ่านข้อมูลจากเว็บเซิร์ฟเวอร์ แต่ฉันพบว่าเซิร์ฟเวอร์ได้รับ URL เดียวเท่านั้นและตราบใดที่คำขอนั้นยังไม่สมบูรณ์ URL ที่สองของ AsyncTask จะไม่ตีเซิร์ฟเวอร์ . คำตอบของคุณแก้ไขปัญหาของฉัน :)
Santhosh Gutta

1
ฉันได้รับ upvotes ต่อไปนี้ขอบคุณ :) แต่ฉันคนประทับใจจำนวนมากพยายามที่จะใช้ AsyncTask สำหรับเครือข่ายที่ห้องสมุดที่ยอดเยี่ยมเช่นวอลเล่ย์ , ฉุยฉายหรือวางท่าเป็นทางเลือกที่ดีกว่า ให้แน่ใจว่าจะตรวจสอบเหล่านี้ก่อนที่จะใช้ AsyncTask :)
sulai

21

การทำข้อเสนอแนะ @sulai ทั่วไปมากขึ้น:

@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
public static <T> void executeAsyncTask(AsyncTask<T, ?, ?> asyncTask, T... params) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}   

โซลูชันทั่วไปเพิ่มเติม: AsyncTaskCompat.executeParallel (ใหม่ MyAsyncTask, myprams);
Vikash Kumar Verma

11

เพียงรวมการอัปเดตล่าสุด (UPDATE 4) ในคำตอบที่ไม่มีที่ติของ @Arhimed ในบทสรุปที่ดีมากของ @sulai:

void doTheTask(AsyncTask task) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Android 4.4 (API 19) and above
        // Parallel AsyncTasks are possible, with the thread-pool size dependent on device
        // hardware
        task.execute(params);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // Android 3.0 to
        // Android 4.3
        // Parallel AsyncTasks are not possible unless using executeOnExecutor
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    } else { // Below Android 3.0
        // Parallel AsyncTasks are possible, with fixed thread-pool size
        task.execute(params);
    }
}

โอ้ว้าวน่ากลัว ซึ่งหมายความว่า.executeOnExecutor()ตอนนี้ซ้ำซ้อนสำหรับ API> = KITKAT ใช่มั้ย
Taslim Oseni

4

ตัวอย่างนักพัฒนา android ของการโหลดบิตแมปอย่างมีประสิทธิภาพใช้ asynctask แบบกำหนดเอง (คัดลอกจาก jellybean) เพื่อให้คุณสามารถใช้ executeOnExecutor ใน apis ต่ำกว่า <11

http://developer.android.com/training/displaying-bitmaps/index.html

ดาวน์โหลดรหัสและไปที่แพคเกจ util


3

มันเป็นไปได้ รุ่นอุปกรณ์ Android ของฉันคือ 4.0.4 และ android.os.Build.VERSION.SDK_INT คือ 15

ฉันมี 3 เหยื่อ

Spinner c_fruit=(Spinner) findViewById(R.id.fruits);
Spinner c_vegetable=(Spinner) findViewById(R.id.vegetables);
Spinner c_beverage=(Spinner) findViewById(R.id.beverages);

และฉันก็มีคลาส Async-Tack

นี่คือรหัสโหลดสปินเนอร์ของฉัน

RequestSend reqs_fruit = new RequestSend(this);
reqs_fruit.where="Get_fruit_List";
reqs_fruit.title="Loading fruit";
reqs_fruit.execute();

RequestSend reqs_vegetable = new RequestSend(this);
reqs_vegetable.where="Get_vegetable_List";
reqs_vegetable.title="Loading vegetable";
reqs_vegetable.execute();

RequestSend reqs_beverage = new RequestSend(this);
reqs_beverage.where="Get_beverage_List";
reqs_beverage.title="Loading beverage";
reqs_beverage.execute();

มันทำงานได้อย่างสมบูรณ์แบบ หนึ่งโดยหนึ่งเหยื่อของฉันโหลด ฉันไม่ได้ใช้งาน executeOnExecutor

นี่คือคลาส Async-task ของฉัน

public class RequestSend  extends AsyncTask<String, String, String > {

    private ProgressDialog dialog = null;
    public Spinner spin;
    public String where;
    public String title;
    Context con;
    Activity activity;      
    String[] items;

    public RequestSend(Context activityContext) {
        con = activityContext;
        dialog = new ProgressDialog(activityContext);
        this.activity = activityContext;
    }

    @Override
    protected void onPostExecute(String result) {
        try {
            ArrayAdapter<String> adapter = new ArrayAdapter<String> (activity, android.R.layout.simple_spinner_item, items);       
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            spin.setAdapter(adapter);
        } catch (NullPointerException e) {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        } catch (Exception e)  {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }
        super.onPostExecute(result);

        if (dialog != null)
            dialog.dismiss();   
    }

    protected void onPreExecute() {
        super.onPreExecute();
        dialog.setTitle(title);
        dialog.setMessage("Wait...");
        dialog.setCancelable(false); 
        dialog.show();
    }

    @Override
    protected String doInBackground(String... Strings) {
        try {
            Send_Request();
            } catch (NullPointerException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        return null;
    }

    public void Send_Request() throws JSONException {

        try {
            String DataSendingTo = "http://www.example.com/AppRequest/" + where;
            //HttpClient
            HttpClient httpClient = new DefaultHttpClient();
            //Post header
            HttpPost httpPost = new HttpPost(DataSendingTo);
            //Adding data
            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);

            nameValuePairs.add(new BasicNameValuePair("authorized","001"));

            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
            // execute HTTP post request
            HttpResponse response = httpClient.execute(httpPost);

            BufferedReader reader;
            try {
                reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                StringBuilder builder = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    builder.append(line) ;
                }

                JSONTokener tokener = new JSONTokener(builder.toString());
                JSONArray finalResult = new JSONArray(tokener);
                items = new String[finalResult.length()]; 
                // looping through All details and store in public String array
                for(int i = 0; i < finalResult.length(); i++) {
                    JSONObject c = finalResult.getJSONObject(i);
                    items[i]=c.getString("data_name");
                }

            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2
คุณจะเห็นได้อย่างไรว่าพวกเขาไม่ได้ดำเนินการตามลำดับ
behelit

@behelit มันสำคัญไหมเมื่อมันทำงาน? โปรดอ่านคำตอบที่ได้รับการยอมรับสำหรับรายละเอียดเพิ่มเติมstackoverflow.com/a/4072832/2345900
Sajitha Rathnayake

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

1
"หนึ่งโดยหนึ่งเหยื่อของฉันโหลด" คุณพูดด้วยตัวเองว่านี่เป็นการประมวลผลแบบอนุกรมไม่ใช่การประมวลผลแบบขนาน AsyncTasks ของเราแต่ละคนจะถูกเพิ่มเข้าไปในคิวและดำเนินการตามลำดับ นี่ไม่ได้ตอบคำถามของ OP
Joshua Pinter

นี่เป็นคำตอบที่เก่ามาก หากนี่ไม่ได้ผลสำหรับคุณโปรดอ้างอิงคำตอบที่ยอมรับ
Sajitha Rathnayake

3

หากคุณต้องการทำงานแบบขนานคุณต้องเรียกใช้เมธอดexecuteOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name")หลัง Android เวอร์ชัน 3.0 แต่วิธีนี้ไม่มีอยู่ก่อน Android 3.0 และหลัง 1.6 เพราะมันทำงานขนานกันด้วยตัวเองดังนั้นฉันขอแนะนำให้คุณปรับแต่งคลาส AsyncTask ของคุณเองในโครงการของคุณเพื่อหลีกเลี่ยงข้อยกเว้นในรุ่น Android ที่แตกต่างกัน

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