淺入淺出Future Pattern
Rick
Long Task
-
IO Bound Task(檔案處理、資料庫讀取)
-
非同步處理(檔案上傳、下載)
-
運算處理(影像處理、數學運算)
-
排程(時間任務)
-
背景服務(Service)
處理Long Task
Image img = downloadImg(url);//10秒(或更久)
handleImg(img);
handleOtherTask();//1秒
Image img = downloadImg(url);//10秒(或更久)
handleImg(img);
handleOtherTask();//2秒
在UI Thread處理Long Task
會造成使用者體驗不佳
使用Thread處理Long Task
new Thread(new Runnable() {
@Override
public void run() {
Image img = downloadImg(url);//10秒(或更久)
handleImg(img);
}
}).start();
handleOtherTask();//1秒
new Thread(new Runnable() {
@Override
public void run() {
Image img = downloadImg(url);//10秒(或更久)
handleImg(img);
}
}).start();
handleOtherTask();//2秒
- 不易重用
- 管理困難
- 無法控制順序
- 要處理非同步問題
- 容易產生Callback Hell
什麼是Future Pattern
Future Pattern是一種非同步處理的程式習慣, 一開始會先讓你取得目標物件, 但是內容尚未取得,
此時可以先去處理其他事情, 等待結果處理完畢後, 再透過目標物件內的方法將結果取出。
舉例來說
到電影院看電影, 你會先拿到一張票(目標物件), 此時電影尚未開始, 因此先去樓下逛街或者買東西(做其他事情), 等到時間到了, 憑著這張票就可以進場看電影(實際的資料)。
Client
Service
Data
call
other call
other call return
call return
等待時間
這是一段非常耗時的程式
其他工作
其他工作
依序呼叫階段
Client
Service
Data
call
other call
other call return
立即返回
這是一段非常耗時的程式
其他工作
其他工作
Future Pattern
程式內怎麼表示呢?
Request
Client
Server
Thread
Future Data
Real Data
Result
Future Object
public class FutureData implements Data{
protected RealData mRealData;
public synchronized void setRealData(RealData realData){
mRealData = realData;
//...
}
public synchronized String getResult() {
//...
return mRealData.result;
}
}
public interface Data {
public String getResult();
}
public class RealData implements Data{
protected String result;
public RealData() {}
public void doTask(){
//..
}
@Override
public String getResult() {
return result;
}
}
Client處理Future Object
public class Client {
private FutureData mFutureData;
public Client(){
mFutureData = new FutureData();
}
public Data request(){
new Thread(){
@Override
public void run() {
RealData realData = new RealData();
realData.doTask();
mFutureData.setRealData(realData);
}
}.start();
return mFutureData;
}
}
Java提供內建的Future
Interface Future<V>
All Known Implementing Classes:
ForkJoinTask, FutureTask, RecursiveAction, RecursiveTask, SwingWorker
FutureTask(Callable<V> callable)
FutureTask(Runnable runnable, V result)
搭配Thread一起服用
FutureTask<Void> futureTask = new FutureTask<>(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hi");
}, null);
new Thread(futureTask).start();
try {
futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
AsyncTask也使用Future Pattern
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
Future Patternの優缺點
- 非同步處理Task
- 可讀性高
- 方法簡單容易操作
- 不易管理
- 不好重組再利用
如果多個任務只要一個完成就處理怎麼做?
如果想要全部結果完成才處理怎麼做?
如果想要回來一個就處理一個怎麼做?
透過內建ThreadPool操作
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Future<?> future = threadPool.submit(()->System.out.println("hi"));
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
public interface ExecutorService extends Executor {
//...
Future<?> submit(Runnable task);
//...
}
ExecutorService の invokeAll
ExecutorService threadPool = Executors.newCachedThreadPool();
List<Callable<Integer>> taskList = new ArrayList<>();
taskList.add(()-> {
System.out.println("1");
return 1;
});
taskList.add(()-> {
System.out.println("2");
return 2;
});
taskList.add(()-> {
System.out.println("3");
return 3;
});
try {
List<Future<Integer>> futureList = threadPool.invokeAll(taskList);
} catch (InterruptedException e) {
e.printStackTrace();
}
輸出順序每次都不一樣
ExecutorService の invokeAny
ExecutorService threadPool = Executors.newCachedThreadPool();
List<Callable<Integer>> taskList = new ArrayList<>();
taskList.add(()-> 1);
taskList.add(()-> 2);
taskList.add(()-> 3);
try {
Integer value = threadPool.invokeAny(taskList);
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
只要一個回來就結束
如果你想要回來一個Task處理一個Task, 可以使用ExecutorCompletionService
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorCompletionService<String> completionService =
new ExecutorCompletionService<>(executorService);
for(int i = 0; i < 100; i++) {
completionService.submit(getTask(i));
}
//...
String s = completionService.take().get();
你可以用單執行緒, 那就確保送進去的任務按照順序回來, 如果是用多執行緒, 就無法確保回來的順序。
Thread處理循序2個Task
new Thread(new Runnable() {
@Override
public void run() {
//處理Task
runOnUiThread(new Runnable() {
@Override
public void run() {
//更新UI
new Thread(new Runnable() {
@Override
public void run() {
//處理Task
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
}
}).start();
}
});
}
}).start();
- Thread管理不便
- 無法重組再利用
- 可讀性不高
- 階層太多容易迷路
Executors處理2個Task
ExecutorService singleThread = Executors.newSingleThreadExecutor();
singleThread.submit(new Runnable() {
@Override
public void run() {
//處理Task
runOnUiThread(new Runnable() {
@Override
public void run() {
//處理Ui
}
});
}
});
singleThread.submit(new Runnable() {
@Override
public void run() {
//處理Task
runOnUiThread(new Runnable() {
@Override
public void run() {
//處理Ui
}
});
}
});
- 邏輯清楚
- 可讀性高
- 方便管理
夠潮
但是其實這樣還是會有問題!
- 任務與任務之間的彼此不相關, 造成前一任務的結果必須透過變數來進行連結, 造成複雜度變高以及過於耦合難以重用。
- 如果任務之間必須傳入類似的非同步物件, 則會造成Callback hell。
Callback Hell
readFileAsync(args[0],
content -> processContentAsync(content,
processedContent -> handleData(content,
c-> out.println(c) ,
ex -> ex.printStackTrace(), service) ,
ex -> ex.printStackTrace(), service),
ex -> ex.printStackTrace(), service);
CompletableFuture的專業處理
CompletableFuture
.runAsync(() -> task1)
.thenRunAsync(() -> task2)
.thenRunAsync(() -> task3)
.whenComplete((r, ex) -> System.out.println("done"));
CompletableFuture可以處理多個非同步!
CompletableFuture有以下的概念
- Completable 確保Future可以被完成
- Listenable 利用callback完成好了叫我
- Composible 向串聯一樣一個成功在處理下一個
- Combinable 同時並行不一定每一個都成功
Completable
public static CompletableFuture<Void> runAsync(Runnable runnable) {
CompletableFuture<Void> future = new CompletableFuture<>();
ForkJoinPool.commonPool().execute(() -> {
try {
runnable.run();
future.complete(null);
} catch (Throwable throwable) {
future.completeExceptionally(throwable);
}
});
return future;
}
//...
CompletableFuture.runAsync(()->handleTask(1)).get();
確保Future可被完成, 或者錯誤處理。
(保證Future原本有的功能存在)
Listenable
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
System.out.println("hello");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).whenComplete((result, throwable) -> {
System.out.println("world");
});
加入了Callback來監聽事件
Composible
public static void main(String[] args) {
CompletableFuture
.completedFuture(handleTask(1))
.thenApply(result->{
if(result){
System.out.println("task 1 success");
return handleTask(2);
}
System.out.println("task 1 fail");
return result;
})
.thenApply(result -> {
if(result){
System.out.println("task 2 success");
return handleTask(3);
}
System.out.println("task 2 fail");
return false;
})
.whenComplete((result, ex) -> {
if(result) {
System.out.println("task 3 success");
} else {
System.out.println("task 3 fail");
return;
}
System.out.println("all task done");
});
}
public static boolean handleTask(int task){
if(task == 1){return false;}
return true;
}
輸出結果
task 1 fail
task 2 fail
task 3 fail
if(task == 1){return false;}
if(task == 2){return false;}
輸出結果
task 1 success
task 2 fail
task 3 fail
if(task == 3){return false;}
輸出結果
task 1 success
task 2 success
task 3 fail
Combinable
CompletableFuture<List<Task>> allDoneFuture = sequence(taskList);
//...
private static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v ->
futures.stream().
map(future -> future.join()).
collect(Collectors.<T>toList())
);
}
allOf(...) | 回傳一個future,其中所有的future都完成此future才算完成。 |
anyOf(...) | 回傳一個future,其中任何一個future完成則此future就算完成。 |
CompletableFuture
只能運行在Java 8
Android N 才能使用Java 8
好險有優秀的第三方支援
RetroLambda + RxJava
Observable
public static void main(String[] args) {
List<Task> taskList = new ArrayList<>();
taskList.add(new Task("task 1"));
taskList.add(new Task("task 2"));
taskList.add(new Task("task 3"));
Observable.from(taskList)
.subscribe(new Observer<Task>() {
private boolean isTaskSuccess = true;
@Override
public void onCompleted() {
System.out.println("completed");
}
@Override
public void onError(Throwable e) {
System.out.println("error");
}
@Override
public void onNext(Task task) {
try {
if(!task.call() || !isTaskSuccess){
isTaskSuccess = false;
System.out.println(task.getName() + " failed.");
} else {
System.out.println(task.getName() + " success.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
private static class Task implements Callable<Boolean>{
private String name;
public Task(String name){
this.name = name;
}
public String getName(){return name;}
@Override
public Boolean call() throws Exception {
return handleTask(name);
}
}
public static boolean handleTask(String task){
if(task.equals("task 3")){return false;}
return true;
}
輸出結果
task 1 fail
task 2 fail
task 3 fail
if(task == 1){return false;}
if(task == 2){return false;}
輸出結果
task 1 success
task 2 fail
task 3 fail
if(task == 3){return false;}
輸出結果
task 1 success
task 2 success
task 3 fail
每個語言都有自己的Future Pattern
- Java via java.util.concurrent.Future
- jQuery's Deferred Object is based on the CommonJS Promises/A design.
- Swift
Async framework
FutureKit
FutureLib
結論
-
善用設計模式建構程式架構, 可以改善可讀性, 可維護性以及彈性。
Thank you.
淺入淺出Future Pattern
By givemepass
淺入淺出Future Pattern
- 1,456