AsyncTask的doInBackground方法延遲執行的問題
AsyncTask引發的一個問題
上週遇到了一個極其詭異的問題,一個小功能從網路上下載一個圖片,然後放到ImageView中,是用AsyncTask來實現的,本身邏輯也很簡單,僅是在doInBackground中用HTTP請求把圖片的輸入流取出,然後用BitmapFactory去解析,然後再把得到的Bitmap放到ImageView中。這個應用是用4.0的SDK開發的,也是執行在4.0上面的。但是有時候下載這張圖片去要用很久很久,甚至要等上幾分鐘。通過除錯發現一個令人難以接受的事實:竟然是doInBackground()未及時執行,也就是它並沒有在#execute()呼叫之後馬上執行,而是等待了很久才得以執行。
神馬情況,難道AsyncTask不是執行緒,難道不是非同步,難道AsyncTask另有內幕?
AsyncTask的內幕
AsyncTask主要有二個部分:一個是與主線各的互動,另一個就是執行緒的管理排程。雖然可能多個AsyncTask的子類的例項,但是AsyncTask的內部Handler和ThreadPoolExecutor都是程序範圍內共享的,其都是static的,也即屬於類的,類的屬性的作用範圍是CLASSPATH,因為一個程序一個VM,所以是AsyncTask控制著程序範圍內所有的子類例項。
與主執行緒互動
與主執行緒互動是通過Handler來進行的,因為本文主要探討AsyncTask在任務排程方面的,所以對於這部分不做細緻介紹,感興趣的朋友可以去看
執行緒任務的排程
內部會建立一個程序作用域的執行緒池來管理要執行的任務,也就就是說當你呼叫了AsyncTask#execute()後,AsyncTask會把任務交給執行緒池,由執行緒池來管理建立Thread和執行Therad。對於內部的執行緒池不同版本的Android的實現方式是不一樣的:
Android2.3以前的版本,也即SDK/API 10和以前的版本
內部的執行緒池限制是5個,也就是說同時只能有5個執行緒執行,超過的執行緒只能等待,等待前面的執行緒某個執行完了才被排程和執行。換句話說,如果一個程序中的AsyncTask例項個數超過5個,那麼假如前5個都執行很長時間的話,那麼第6個只能等待機會了。這是AsyncTask的一個限制,而且對於2.3以前的版本無法解決。如果你的應用需要大量的後臺執行緒去執行任務,那麼你只能放棄使用AsyncTask,自己建立執行緒池來管理Thread,或者乾脆不用執行緒池直接使用Thread也無妨。不得不說,雖然AsyncTask較Thread使用起來比較方便,但是它最多隻能同時執行5個執行緒,這也大大侷限了它的實力,你必須要小心的設計你的應用,錯開使用AsyncTask的時間,盡力做到分時,或者保證數量不會大於5個,否則就可能遇到上面提到的問題。要不然就只能使用JavaSE中的API了。
Android 3.0以後,也即SDK/API 11和以後的版本
可能是Google意識到了AsyncTask的侷限性了,從Android 3.0開始對AsyncTask的API做出了一些調整:
- #execute()提交的任務,按先後順序每次只執行一個
也就是說它是按提交的次序,每次只啟動一個執行緒執行一個任務,完成之後再執行第二個任務,也就是相當於只有一個後臺執行緒在執行所提交的任務(Executors.newSingleThreadPool())。
- 新增了介面#executeOnExecutor()
這個介面允許開發者提供自定義的執行緒池來執行和排程Thread,如果你想讓所有的任務都能併發同時執行,那就建立一個沒有限制的執行緒池(Executors.newCachedThreadPool()),並提供給AsyncTask。這樣這個AsyncTask例項就有了自己的執行緒池而不必使用AsyncTask預設的。
- 新增了二個預定義的執行緒池SERIAL_EXECUTOR和THREAD_POOL_EXECUTOR
其實THREAD_POOL_EXECUTOR並不是新增的,之前的就有,只不過之前(Android 2.3)它是AsyncTask私有的,未公開而已。THREAD_POOL_EXECUTOR是一個corePoolSize為5的執行緒池,也就是說最多隻有5個執行緒同時執行,超過5個的就要等待。所以如果使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)就跟2.3版本的AsyncTask.execute()效果是一樣的。
而SERIAL_EXECUTOR是新增的,它的作用是保證任務執行的順序,也就是它可以保證提交的任務確實是按照先後順序執行的。它的內部有一個佇列用來儲存所提交的任務,保證當前只執行一個,這樣就可以保證任務是完全按照順序執行的,預設的execute()使用的就是這個,也就是executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)與execute()是一樣的。
前面問題的解法
瞭解了AsyncTask的內幕就知道了前面問題的原因:因為是4.0平臺,所以所有的AsyncTask並不都會執行在單獨的執行緒中,而是被SERIAL_EXECUTOR順序的使用執行緒執行。因為應用中可能還有其他地方使用AsyncTask,所以到網路取圖片的AsyncTask也許會等待到其他任務都完成時才得以執行而不是呼叫executor()之後馬上執行。
那麼解決方法其實很簡單,要麼直接使用Thread,要麼建立一個單獨的執行緒池(Executors.newCachedThreadPool())。或者最簡單的解法就是使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),這樣起碼不用等到前面的都結束了再執行。
AsyncTask的使用注意事項
前面的文章曾建議使用AsyncTask而不是使用Thread,但是AsyncTask似乎又有它的限制,這就要根據具體的需求情況而選擇合適的工具,No Silver Bullet。下面是一些建議:
- 改善你的設計,少用非同步處理
執行緒的開銷是非常大的,同時非同步處理也容易出錯,難除錯,難維護,所以改善你的設計,儘可能的少用非同步。對於一般性的資料庫查詢,少量的I/O操作是沒有必要啟動執行緒的。
- 與主執行緒有互動時用AsyncTask,否則就用Thread
AsyncTask被設計出來的目的就是為了滿足Android的特殊需求:非主執行緒不能操作(UI)元件,所以AsyncTask擴充套件Thread增強了與主執行緒的互動的能力。如果你的應用沒有與主執行緒互動,那麼就直接使用Thread就好了。
- 當有需要大量執行緒執行任務時,一定要建立執行緒池
執行緒的開銷是非常大的,特別是建立一個新執行緒,否則就不必設計執行緒池之類的工具了。當需要大量執行緒執行任務時,一定要建立執行緒池,無論是使用AsyncTask還是Thread,因為使用AsyncTask它內部的執行緒池有數量限制,可能無法滿足需求;使用Thread更是要執行緒池來管理,避免虛擬機器建立大量的執行緒。比如從網路上批量下載圖片,你不想一個一個的下,或者5個5個的下載,那麼就建立一個CorePoolSize為10或者20的執行緒池,每次10個或者20個這樣的下載,即滿足了速度,又不至於耗費無用的效能開銷去無限制的建立執行緒。
- 對於想要立即開始執行的非同步任務,要麼直接使用Thread,要麼單獨建立執行緒池提供給AsyncTask
預設的AsyncTask不一定會立即執行你的任務,除非你提供給他一個單獨的執行緒池。如果不與主執行緒互動,直接建立一個Thread就可以了,雖然建立執行緒開銷比較大,但如果這不是批量操作就沒有問題。
- Android的開發沒有想像中那樣簡單,要多花心思和時間在程式碼上和測試上面,以確信程式是優質的
附上相關資源:
使用自定義的CorePoolSize為7的Executor(Executors.newFixedThreadPool(7)):
使用未設限制的Executor(Executors.newCachedThreadPool()):
這些例子所用的程式碼:
[java] view plaincopyprint?- publicclass AsyncTaskDemoActivity extends Activity {
- privatestaticint ID = 0;
- privatestaticfinalint TASK_COUNT = 9;
- privatestatic ExecutorService SINGLE_TASK_EXECUTOR;
- privatestatic ExecutorService LIMITED_TASK_EXECUTOR;
- privatestatic ExecutorService FULL_TASK_EXECUTOR;
- static {
- SINGLE_TASK_EXECUTOR = (ExecutorService) Executors.newSingleThreadExecutor();
- LIMITED_TASK_EXECUTOR = (ExecutorService) Executors.newFixedThreadPool(7);
- FULL_TASK_EXECUTOR = (ExecutorService) Executors.newCachedThreadPool();
- };
- @Override
- publicvoid onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.asynctask_demo_activity);
- String title = "AsyncTask of API " + VERSION.SDK_INT;
- setTitle(title);
- final ListView taskList = (ListView) findViewById(R.id.task_list);
- taskList.setAdapter(new AsyncTaskAdapter(getApplication(), TASK_COUNT));
- }
- privateclass AsyncTaskAdapter extends BaseAdapter {
- private Context mContext;
- private LayoutInflater mFactory;
- privateint mTaskCount;
- List<SimpleAsyncTask> mTaskList;
- public AsyncTaskAdapter(Context context, int taskCount) {
- mContext = context;
- mFactory = LayoutInflater.from(mContext);
- mTaskCount = taskCount;
- mTaskList = new ArrayList<SimpleAsyncTask>(taskCount);
- }
- @Override
- publicint getCount() {
- return mTaskCount;
- }
- @Override
- public Object getItem(int position) {
- return mTaskList.get(position);
- }
- @Override
- publiclong getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = mFactory.inflate(R.layout.asynctask_demo_item, null);
- SimpleAsyncTask task = new SimpleAsyncTask((TaskItem) convertView);
- /*
- * It only supports five tasks at most. More tasks will be scheduled only after
- * first five finish. In all, the pool size of AsyncTask is 5, at any time it only
- * has 5 threads running.
- */
- // task.execute();
- // use AsyncTask#SERIAL_EXECUTOR is the same to #execute();
- // task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
- // use AsyncTask#THREAD_POOL_EXECUTOR is the same to older version #execute() (less than API 11)
- // but different from newer version of #execute()
- // task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- // one by one, same to newer version of #execute()
- // task.executeOnExecutor(SINGLE_TASK_EXECUTOR);
- // execute tasks at some limit which can be customized
- // task.executeOnExecutor(LIMITED_TASK_EXECUTOR);
- // no limit to thread pool size, all tasks run simultaneously
- task.executeOnExecutor(FULL_TASK_EXECUTOR);
- mTaskList.add(task);
- }
- return convertView;
- }
- }
- privateclass SimpleAsyncTask extends AsyncTask<Void, Integer, Void> {
- private TaskItem mTaskItem;
- private String mName;
- public SimpleAsyncTask(TaskItem item) {
- mTaskItem = item;
-
相關推薦
AsyncTask的doInBackground方法延遲執行的問題
近日開發遇到AsyncTask的doInBackground()方法不執行的問題,所以在網上查詢原因,以下博文解決了我的問題,我用Thread代替了AysncTask進行工作。博文如下: 地址:http://blog.csdn.net/hitlion2008/article/details/7983
[Xcode10 實際操作]八、網路與多執行緒-(19)使用RunLoop使PerformSelector方法延遲動作的執行
本文將演示使用RunLoop使PerformSelector方法延遲動作的執行。 在專案導航區,開啟檢視控制器的程式碼檔案【ViewController.swift】 1 import UIKit 2 3 class ViewController: UIViewController {
Objective-C延遲執行方法總結
1.performSelector 這種方法使用起來比較方便: [self performSelector:@selector(delayAction) withObject:nil afterDelay:2]; 系統也提供了取消要執行的方法
iOS延遲執行的三種方法
第一種: [selfperformSelector:@selector(afterDelayMethod) withObject:selfafterDelay:3]; 第二種: //[NSThread sleepForTimeInterval:3];//主執行緒沉睡
使用android中的handler延遲執行方法
android中的handler可以用來延遲執行函式,例如在介面載入完成後10秒種執行一個方法A,可以如下: private Handler mHandler = new Handler() { public void dispatchMessa
[ios]延遲執行方法,取消執行方法
延遲執行 [self performSelector:@selector(turnBlue) withObject:nil afterDelay:0.1f]; 延遲執行之前可以取消該方法的執行 [[self class] cancelPreviousPerformReque
ios的延遲執行方法
轉載源地址:http://poolo.iteye.com/blog/1820536 1.最直接的方法performSelector:withObject:afterDelay: 這種方法的缺點:每次要為延時寫一個方法 2.使用類別,用BOLCK執行 [程式碼]c
Android 延遲執行某方法
1,開啟新執行緒 new Thread(new Runnable(){ public void run(){ Thread.sleep(XXXX); handler.sendMessage(); //告訴主執行緒執
Oracle 存儲過程 延遲執行 DBMS_LOCK.SLEEP(60);
nbsp to_char ng- pos variables div char str 延遲 --測試代碼: declare -- Local variables here i integer; begin -- Test s
WebDriver提供了executeScript()方法來執行JavaScript代碼
executescript()方法WebDriver提供了executeScript()方法來執行JavaScript代碼業務場景:打開百度網頁,隱藏百度一下按鈕,因為裏面涉及到按鈕的屬性,所以要用js.方法:(JavascriptExecutor)driver.executesScript();本文出自 “
Action 方法的執行(一)
數組 ttr 獲取 resolv static lte com nag binding [toc] 在 Aps.net mvc 應用中對請求的處理最終都是轉換為對某個 Controller 中的某個 Action 方法的調用,因此,要對一個請求進行處理,第一步,需要根據
JS延遲執行
java script timeout func clas 延遲執行 rip head fun <!DOCTYPE html> <html> <head> <title></title>
SpringAOP來監控service層中每個方法的執行時間
error try ref args 方法名 str ogg object PE 使用AOP來說,太方便了,並且特別適合這類場景。 代碼如下,這裏是將要統計的信息寫到log文件中,也可以設計成寫入表中。 package com.ecsoft.interceptor; im
多繼承父類構造方法的執行順序
code pri span 都是 例子 父類 一次 沒有 pan 1 class a: 2 def __init__(self): 3 print(‘a‘) 4 5 class b(a): 6 def __init__(sel
python:discover()方法批量執行用例
runner 收集 使用 子目錄 utils 每次 框架 ner docs 自動化測試過程中,自動化覆蓋的功能點和對應測試用例之間的關系基本都是1 VS N,如果每次將測試用例一個個單獨執行,不僅效率很低, 無法快速反饋測試結果,而且維護起來很麻煩。在python的單元測試
util包下Timer類的延遲執行
schedule 再次 由於 調用次數 原來 一次 nbsp 調用 png Timer類是用來做定時器的關鍵類,其實質是在線程中定時調用任務方法的實現。 timer.schedule(TimerTask,int,int)實現的是循環調用任務方法,在TimerTask中就是一
react 調用 function 的寫法 及 解決 react onClick 方法自動執行
java eve 寫法 UNC als code 情況 自動調用 etc 1.react 調用方法的寫法 (1)方式一 onClick={this.getFetchData.bind(this,item.id)} (2)方式二 getFetchData(e){ t
繼承(構造方法的執行)
main .com name 運行 code rgs [] oid stat 父類: public class Demo4 { static{ System.out.println("Demo4靜態代碼塊執行了"); } {
java語言基礎--方法的執行圖解
bsp 圖片 mage .com http 運行 分享 調用 執行 1.調用m1方法(壓棧) 2.m1調用m2方法(壓棧) 3.m2調用m3方法(壓棧) 4.運行m3(彈棧) 5.運行m2(彈棧) 6.運行m1(彈棧) java語言基礎--方法的執行圖解
struts2,action方法自動執行兩次
sys sele urn 自動 obj sel hiberna ron name 發現一個比較有意思的bug, //Action中以get開頭且返回類型不為void(如 public Object get*() )的方法會被自動執行//放入值棧時會執行一次,//若在頁面調