Resubmitting/scheduling task from the task itself - is it a good practice?

Andremoniy :

Consider we have a scheduled executor service:

ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(...);

And for some logic we want to retry a task execution. The following approach seems to be smelling for me, but I can't understand why:

threadPool.submit(new Runnable() {
    @Override
    public void run() {
        // ...
        if (needToBeScheduled()) {
           threadPool.schedule(this, delay, TimeUnit.MINUTES);
        } else if (needToBeResubmitted()) {
           threadPool.submit(this);
        }
    }
});

The one obvious problem I see is that this code is not possible to convert to lambda:

threadPool.submit(()-> {
    // ...
    if (needToBeScheduled()) {
        threadPool.schedule(this, delay, TimeUnit.MINUTES);
    } else if (needToBeResubmitted()) {
        threadPool.submit(this);
    }
});

^^ this won't compile, as we can not refer this from lambda. Though it can be solved by introducing a method which produces such an instance and provide it instead of this.

But this is only one disadvantage I see. Is anything else here which can cause any problems? Perhaps there is a more proper approach? Move this logic to ThreadPoolExecutor.afterExecute() (this causes type conversion though...)?

Assuming that object is stateless, i.e. there are no object variables in Runnable instance.

P.S. The logic of what to do (reschedule task or resubmit or do nothing) is based on some information retrieved from the database (or any external source). So Runnable is still stateless, but it calculates the outcome based on some results of its work.

Andrew Tobilko :

Honestly, I don't like the approach where a task (a simple independent unit of work) decides whether it should put itself in the service or not and interacts with the ExecutorService directly. I believe // ... is the only part a task should execute.

I would convert a Runnable in a Callable<Boolean>:

Callable<Boolean> task = () -> {
    // ...
    return needToBeScheduled; // or sth more complex with several boolean fields
};

And I would definitely move that logic outside a task (for example, into a service method):

Future<Boolean> future = threadPool.submit(task);

try {
    boolean needToBeScheduled = future.get();

    if (needToBeScheduled) {
        threadPool.schedule(task, delay, TimeUnit.MINUTES);
    }
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

By something more complex I meant a class that comprises 2 boolean fields. It takes Supplier<Boolean>s to make things lazy.

final class TaskResult {
    private final Supplier<Boolean> needToBeScheduled;
    private final Supplier<Boolean> needToBeResubmitted;

    private TaskResult(Supplier<Boolean> needToBeScheduled, Supplier<Boolean> needToBeResubmitted) {
        this.needToBeScheduled = needToBeScheduled;
        this.needToBeResubmitted = needToBeResubmitted;
    }

    public static TaskResult of(Supplier<Boolean> needToBeScheduled, Supplier<Boolean> needToBeResubmitted) {
        return new TaskResult(needToBeScheduled, needToBeResubmitted);
    }

    public boolean needToBeScheduled() {
        return needToBeScheduled != null && needToBeScheduled.get();
    }

    public boolean needToBeResubmitted() {
        return needToBeResubmitted != null && needToBeResubmitted.get();
    }
}

With a few changes to the above example, we have:

Callable<TaskResult> task = () -> {
    // ...
    return TaskResult.of(() -> needToBeScheduled(), () -> needToBeResubmitted());
};

final Future<TaskResult> future = threadPool.submit(task);

try {
    final TaskResult result = future.get();

    if (result.needToBeScheduled()) {
        threadPool.schedule(task, delay, TimeUnit.MINUTES);
    }

    if (result.needToBeResubmitted()) {
        threadPool.submit(task);
    }
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=464079&siteId=1