如何使用ExecutorCompletionService

如何使用ExecutorCompletionService

如何使用Future Pattern-2中, 我們示範了Future Pattern,
並且使用了InvokeAll跟InvokeAny兩種方式,
但是會想說怎麼沒有一種方式可以任務處理完就先回來,
其實有! 你可以利用ExecutorCompletionService來達成這樣的實做。

首先建立一個Button跟一個TextView,
用來模擬傳送Task到遠端, 然後結果回來顯示在TextView上面。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/start_task"
        android:text="start task"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
    <TextView
        android:layout_below="@id/start_task"
        android:id="@+id/result"
        android:text="Hello World!"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</RelativeLayout>


接著我們在Java檔案內進行操作

public class MainActivity extends AppCompatActivity {
    private Button startTask;
    private TextView result;
    private CompletionService<String> completionService;
    private ExecutorService executorService;
    private StringBuffer strBuffer;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        initView();
    }

    private void initData(){
        executorService = Executors.newCachedThreadPool();
        completionService = new ExecutorCompletionService<String>(executorService);
        strBuffer = new StringBuffer();
    }

    private void initView(){
        startTask = (Button) findViewById(R.id.start_task);
        result = (TextView) findViewById(R.id.result);
        startTask.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ExecutorService singleThread = Executors.newSingleThreadExecutor();
                singleThread.submit(new Runnable() {
                    @Override
                    public void run() {
                        strBuffer.delete(0, strBuffer.length());
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                result.setText(strBuffer.toString());
                            }
                        });
                        for(int i = 0; i < 10; i++) {
                            completionService.submit(getTask(i));
                        }
                        for(int i = 0; i < 10; i++) {
                            try {
                                String s = completionService.take().get();
                                strBuffer.append(s + "\n");
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        result.setText(strBuffer.toString());
                                    }
                                });

                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            } catch (ExecutionException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });

            }
        });
    }

    private Callable<String> getTask(final int index){
        return new Callable<String>() {
            @Override
            public String call() throws Exception {
                int r = (int)(Math.random() * 3 + 1);
                Thread.sleep(r * 1000);
                return "task " + index;
            }
        };
    }
}

由上面可以看到, 我們宣告了一個newCachedThreadPool,
接著把它塞入到ExecutorCompletionService,
這樣就可以批次處理多個Task。

executorService = Executors.newCachedThreadPool();
completionService = new ExecutorCompletionService<String>(executorService);

接著我們要讓每個Task都有自己的編號,
因此在呼叫Callable的時候, 會帶入一個index,
讓它隨著String回來, 而利用Thread.sleep()方法,
隨機睡個幾秒, 模擬長任務的操作。

private Callable<String> getTask(final int index){
    return new Callable<String>() {
        @Override
        public String call() throws Exception {
            int r = (int)(Math.random() * 3 + 1);
            Thread.sleep(r * 1000);
            return "task " + index;
        }
    };
}

最後透過completionService將任務送出去,
透過take()的方式把每個完成的任務拿回來,
呈現在TextView上面。

for(int i = 0; i < 10; i++) {
    completionService.submit(getTask(i));
}
for(int i = 0; i < 10; i++) {
    try {
        String s = completionService.take().get();
        strBuffer.append(s + "\n");
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                result.setText(strBuffer.toString());
            }
        });

    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

操作結果如下

程式碼