Giới hạn chuỗi Android AsyncTask?


95

Tôi đang phát triển một ứng dụng mà tôi cần cập nhật một số thông tin mỗi khi người dùng đăng nhập vào hệ thống, tôi cũng sử dụng cơ sở dữ liệu trong điện thoại. Đối với tất cả các hoạt động đó (cập nhật, truy xuất dữ liệu từ db, v.v.), tôi sử dụng các tác vụ không đồng bộ. Cho đến bây giờ tôi không hiểu tại sao tôi không nên sử dụng chúng, nhưng gần đây tôi đã trải nghiệm rằng nếu tôi thực hiện một số thao tác, một số tác vụ không đồng bộ của tôi chỉ dừng lại ở chế độ tiền thực thi và không chuyển sang doInBackground. Điều đó thật quá kỳ lạ khi để nó như vậy, vì vậy tôi đã phát triển một ứng dụng đơn giản khác chỉ để kiểm tra xem có gì sai không. Và kỳ lạ thay, tôi nhận được hành vi tương tự khi tổng số tác vụ không đồng bộ đạt đến 5, tác vụ thứ 6 dừng trước khi thực thi.

Android có giới hạn asyncTasks trên Activity / App không? Hay nó chỉ là một số lỗi và nó phải được báo cáo? Có ai gặp vấn đề tương tự và có thể tìm thấy giải pháp cho nó không?

Đây là mã:

Chỉ cần tạo 5 trong số các chuỗi đó để hoạt động ở chế độ nền:

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

Và sau đó tạo chủ đề này. Nó sẽ vào preExecute và treo (nó sẽ không chuyển đến 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");
    }
}

Câu trả lời:


207

Tất cả các AsyncTasks được kiểm soát nội bộ bởi ThreadPoolExecutor được chia sẻ (tĩnh) và một LinkedBlockingQueue . Khi bạn gọi executemột AsyncTask, AsyncTask ThreadPoolExecutorsẽ thực thi nó khi nó sẵn sàng trong tương lai.

Câu hỏi 'khi nào tôi sẵn sàng?' hành vi của a ThreadPoolExecutorđược kiểm soát bởi hai tham số, kích thước nhóm lõikích thước nhóm tối đa . Nếu có ít hơn các luồng kích thước nhóm lõi hiện đang hoạt động và một công việc mới xuất hiện, người thực thi sẽ tạo một luồng mới và thực thi nó ngay lập tức. Nếu có ít nhất các luồng kích thước nhóm lõi đang chạy, nó sẽ cố gắng xếp hàng đợi công việc và đợi cho đến khi có sẵn một luồng nhàn rỗi (tức là cho đến khi một công việc khác được hoàn thành). Nếu không thể xếp hàng công việc (hàng đợi có thể có dung lượng tối đa), nó sẽ tạo một luồng mới (luồng có kích thước nhóm tối đa) để các công việc chạy trong. Các luồng nhàn rỗi không lõi cuối cùng có thể ngừng hoạt động theo thông số thời gian chờ duy trì.

Trước Android 1.6, kích thước nhóm lõi là 1 và kích thước nhóm tối đa là 10. Kể từ Android 1.6, kích thước nhóm lõi là 5 và kích thước nhóm tối đa là 128. Kích thước của hàng đợi là 10 trong cả hai trường hợp. Thời gian chờ duy trì sự sống là 10 giây trước 2,3 và 1 giây kể từ đó.

Với tất cả những điều này, bây giờ chúng ta đã rõ tại sao ý AsyncTaskchí chỉ xuất hiện để thực thi 5/6 nhiệm vụ của bạn. Nhiệm vụ thứ 6 đang được xếp hàng cho đến khi một trong các nhiệm vụ khác hoàn thành. Đây là một lý do rất tốt tại sao bạn không nên sử dụng AsyncTasks cho các hoạt động chạy dài - nó sẽ ngăn các AsyncTasks khác không bao giờ chạy.

Để hoàn thiện, nếu bạn lặp lại bài tập của mình với nhiều hơn 6 nhiệm vụ (ví dụ: 30), bạn sẽ thấy rằng hơn 6 tác vụ sẽ được nhập vào doInBackgroundvì hàng đợi sẽ trở nên đầy và trình thực thi được thúc đẩy để tạo thêm luồng công nhân. Nếu bạn tiếp tục với nhiệm vụ đang chạy dài hạn, bạn sẽ thấy rằng 20/30 trở nên hoạt động, với 10 vẫn còn trong hàng đợi.


2
"Đây là một lý do rất tốt tại sao bạn không nên sử dụng AsyncTasks cho các hoạt động lâu dài" Khuyến nghị của bạn cho tình huống này là gì? Tạo luồng mới theo cách thủ công hay tạo dịch vụ thực thi của riêng bạn?
user123321

2
Các trình thực thi về cơ bản là một sự trừu tượng hóa trên các luồng, giảm bớt nhu cầu viết mã phức tạp để quản lý chúng. Nó tách các nhiệm vụ của bạn ra khỏi cách chúng sẽ được thực thi. Nếu mã của bạn chỉ phụ thuộc vào một trình thực thi, thì có thể dễ dàng thay đổi rõ ràng số lượng luồng được sử dụng, v.v. Tôi thực sự không thể nghĩ ra lý do chính đáng để tự tạo luồng, vì ngay cả đối với các tác vụ đơn giản, số lượng công việc với một Chấp hành viên cũng vậy, nếu không muốn nói là ít hơn.
antonyt

37
Xin lưu ý rằng từ Android 3.0+, số lượng AsyncTasks mặc định đồng thời đã giảm xuống còn 1. Thông tin thêm: developer.android.com/reference/android/os/…
Kieran

Wow, cảm ơn rất nhiều vì một câu trả lời tuyệt vời. Cuối cùng, tôi có lời giải thích tại sao mã của tôi thất bại một cách lẻ tẻ và bí ẩn.
Chris Knight

@antonyt, Một điều đáng nghi ngờ nữa, AsyncTasks đã bị hủy, nó có được tính vào số AsyncTasks không? tức là, được tính trong core pool sizehoặc maximum pool size?
nmxprime,

9

@antonyt có câu trả lời đúng nhưng trong trường hợp bạn đang tìm kiếm một giải pháp đơn giản thì bạn có thể xem Needle.

Với nó, bạn có thể xác định kích thước nhóm luồng tùy chỉnh và không giống như AsyncTasknó hoạt động trên tất cả các phiên bản Android như nhau. Với nó, bạn có thể nói những điều như:

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

hoặc những thứ như

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

Nó có thể làm được nhiều hơn thế. Kiểm tra nó trên GitHub .


lần cam kết cuối cùng là 5 năm trước :(
LukaszTaraszka

5

Cập nhật : Kể từ API 19, kích thước nhóm luồng lõi đã được thay đổi để phản ánh số lượng CPU trên thiết bị, với tối thiểu là 2 và tối đa là 4 khi bắt đầu, trong khi tăng lên tối đa là CPU * 2 +1 - Tham khảo

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

Cũng lưu ý rằng mặc dù trình thực thi mặc định của AsyncTask là nối tiếp (thực hiện một tác vụ tại một thời điểm và theo thứ tự mà chúng đến), với phương thức

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

bạn có thể cung cấp một Người thừa hành để điều hành các nhiệm vụ của bạn. Bạn có thể cung cấp THREAD_POOL_EXECUTOR trình thực thi ẩn nhưng không cần tuần tự hóa các tác vụ hoặc thậm chí bạn có thể tạo Trình thực thi của riêng mình và cung cấp tại đây. Tuy nhiên, hãy cẩn thận lưu ý cảnh báo trong Javadocs.

Cảnh báo: Việc cho phép nhiều tác vụ chạy song song từ một nhóm luồng thường không phải là điều người ta muốn, vì thứ tự hoạt động của chúng không được xác định. Ví dụ: nếu các tác vụ này được sử dụng để sửa đổi bất kỳ trạng thái chung nào (chẳng hạn như viết tệp do một lần nhấp vào nút), không có đảm bảo nào về thứ tự của các sửa đổi. Nếu không làm việc cẩn thận, trong một số ít trường hợp, phiên bản mới hơn của dữ liệu có thể bị phiên bản cũ hơn ghi quá mức, dẫn đến các vấn đề về độ ổn định và mất dữ liệu bị che khuất. Những thay đổi như vậy được thực hiện tốt nhất trong nối tiếp; để đảm bảo công việc đó được tuần tự hóa bất kể phiên bản nền tảng nào, bạn có thể sử dụng chức năng này với SERIAL_EXECUTOR.

Một điều nữa cần lưu ý là cả khung công tác được cung cấp bởi Người thực thi THREAD_POOL_EXECUTOR và phiên bản nối tiếp của nó SERIAL_EXECUTOR (được mặc định cho AsyncTask) đều là tĩnh (cấu trúc cấp lớp) và do đó được chia sẻ trên tất cả các phiên bản của (các) AsyncTask trong quy trình ứng dụng của bạn.

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.