淺入淺出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

程式範例[github]

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

      Async framework

      FutureKit

      FutureLib

結論

  • 善用設計模式建構程式架構, 可以改善可讀性, 可維護性以及彈性

 

Thank you.

淺入淺出Future Pattern

By givemepass

淺入淺出Future Pattern

  • 1,456