특정 Task를 MainThread와 Background thread를 번갈아가면서 순차적으로 실행해야 하는 경우, Task 코드 작성을 쉽게 하기 위한 유틸리티 클래스를 작성해보았다.
예제는...
new MainThreadTask<String, Integer>() {
@Override
protected Integer run(String param) {
TraceUtils.v("1 MainThreadTask=%s", param);
SystemClock.sleep(1000);
return 123;
}
}.connect(new BackgroundTask<Integer, Long>() {
@Override
protected Long run(Integer param) {
TraceUtils.v("2 BackgroundTask %d", param);
SystemClock.sleep(1000);
return 456L;
}
}).connect(new MainThreadTask<Long, Integer>() {
@Override
protected Integer run(Long param) {
TraceUtils.v("3 MainThreadTask=%d", param);
SystemClock.sleep(1000);
return 789;
}
}).connect(new BackgroundTask<Integer, Void>() {
@Override
protected Void run(Integer param) {
TraceUtils.v("4 Background Long=%s", param);
SystemClock.sleep(1000);
return null;
}
}).kick("0");
ConnectiveTask.java
package com.mdiwebma.base.task;
import java.util.concurrent.Executor;
/**
* @author djkim
*/
public abstract class ConnectiveTask<P, R> {
private boolean nextTaskCancelled;
protected ConnectiveTask<R, ?> nextTask;
protected abstract R run(P param);
protected abstract Executor getExecutor();
protected void cancelNextTask() {
nextTaskCancelled = true;
}
public void kick() {
kick(null);
}
public void kick(final P p) {
getExecutor().execute(new Runnable() {
@Override
public void run() {
R result = ConnectiveTask.this.run(p);
if (!nextTaskCancelled && nextTask != null) {
nextTask.kick(result);
}
}
});
}
public <S> TaskConnector<P, R, S> connect(ConnectiveTask<R, S> nextTask) {
this.nextTask = nextTask;
return new TaskConnector<>(this, nextTask);
}
}
MainThreadTask.java
package com.mdiwebma.base.task;
import java.util.concurrent.Executor;
/**
* @author djkim
*/
public abstract class MainThreadTask<P, R> extends ConnectiveTask<P, R> {
private static class MainThreadExecutor implements Executor {
@Override
public void execute(Runnable command) {
CommonExecutors.getHandler().post(command);
}
}
private static final MainThreadExecutor mainThreadExecutor = new MainThreadExecutor();
@Override
protected Executor getExecutor() {
return mainThreadExecutor;
}
}
BackgroundTask.java
package com.mdiwebma.base.task;
import java.util.concurrent.Executor;
/**
* @author djkim
*/
public abstract class BackgroundTask<P, R> extends ConnectiveTask<P, R> {
@Override
protected Executor getExecutor() {
return CommonExecutors.getCachedThreadPool();
}
}
TaskConnector.java
package com.mdiwebma.base.task;
import android.support.annotation.NonNull;
/**
* @author djkim
*/
public class TaskConnector<P, Q, R> {
@NonNull
private final ConnectiveTask<P, ?> initialTask;
@NonNull
private final ConnectiveTask<Q, R> lastTask;
TaskConnector(@NonNull ConnectiveTask<P, ?> initialTask, @NonNull ConnectiveTask<Q, R> nextTask) {
this.initialTask = initialTask;
this.lastTask = nextTask;
}
public <S> TaskConnector<P, R, S> connect(ConnectiveTask<R, S> nextTask) {
this.lastTask.nextTask = nextTask;
return new TaskConnector<>(initialTask, nextTask);
}
public void kick() {
kick(null);
}
public void kick(final P p) {
initialTask.kick(p);
}
}
간단히 구현하려다 보니 여러 문제가 있기는 하다. task를 재 사용할 경우 nextTask를 잘 관리해야 한다. task 하나를 여러번 connect한다던지 하면 문제가 생긴다. 실행이 끝난 task를 다음 번에 다시 사용할 경우 혹은 두 개이상의 Chain에 연결할 경우 문제가 생긴다.
문제를 해결하려면 task 두 개를 연결하는 정보만으로 Chain을 만들어서 kick을 하면 된다.
5/17일 추가
개선된 ConnectiveTask.java
package com.mdiwebma.base.task;
import java.util.concurrent.Executor;
/**
* @author djkim
*/
public abstract class ConnectiveTask<P, R> {
private boolean nextTaskCancelled;
protected abstract R run(P param);
protected abstract Executor getExecutor();
protected void setNextTaskCancelled(boolean cancelled) {
this.nextTaskCancelled = cancelled;
}
protected boolean isNextTaskCancelled() {
return this.nextTaskCancelled;
}
public void kick() {
kick(null);
}
public void kick(final P p) {
getExecutor().execute(new Runnable() {
@Override
public void run() {
R result = ConnectiveTask.this.run(p);
}
});
}
public <S> TaskConnector<P, R, S> connect(ConnectiveTask<R, S> nextTask) {
TaskConnector<P, P, R> initialConnector = new TaskConnector<>(null, this);
TaskConnector<P, R, S> nextConnector = new TaskConnector<>(initialConnector, nextTask);
initialConnector.nextConnector = nextConnector;
return nextConnector;
}
}
개선된 TaskConnector.java
package com.mdiwebma.base.task;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* @author djkim
*/
public class TaskConnector<A, P, Q> {
@NonNull
private final TaskConnector<A, A, ?> initialConnector;
@NonNull
private final ConnectiveTask<P, Q> task;
@Nullable
protected TaskConnector<A, Q, ?> nextConnector;
protected TaskConnector(@NonNull TaskConnector<A, A, ?> initialConnector, @NonNull ConnectiveTask<P, Q> task) {
this.initialConnector = initialConnector;
this.task = task;
}
public <R> TaskConnector<A, Q, R> connect(ConnectiveTask<Q, R> nextTask) {
TaskConnector<A, Q, R> nextConnector = new TaskConnector<>(this.initialConnector, nextTask);
this.nextConnector = nextConnector;
return nextConnector;
}
public void kick() {
kick(null);
}
public void kick(A param) {
initialConnector.execute(initialConnector.getRunnable(param));
}
void execute(Runnable runnable) {
task.getExecutor().execute(runnable);
}
Runnable getRunnable(P param) {
return new ConnectiveRunnable(param);
}
protected class ConnectiveRunnable implements Runnable {
final P param;
ConnectiveRunnable(P param) {
this.param = param;
}
@Override
public void run() {
task.setNextTaskCancelled(false);
Q result = task.run(this.param);
if (!task.isNextTaskCancelled() && nextConnector != null) {
nextConnector.execute(nextConnector.getRunnable(result));
}
}
}
}
개선된 예제
final BackgroundTask<Integer, Long> back1 = new BackgroundTask<Integer, Long>() {
@Override
protected Long run(Integer param) {
TraceUtils.v("BackgroundTask Integer=%d", param);
SystemClock.sleep(1000);
return param + 1L;
}
};
MainThreadTask<Long, Integer> back2 = new MainThreadTask<Long, Integer>() {
@Override
protected Integer run(Long param) {
TraceUtils.v("MainThreadTask Long=%d", param);
SystemClock.sleep(1000);
return (int) (param + 1);
}
};
back2.connect(back1)
.connect(back2).connect(back1)
.connect(back2).connect(back1)
.connect(back2).connect(back1)
.connect(back2).connect(back1)
.connect(back2)
.kick(0L);