Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Submitting a background task from spring mvc app

I've got working spring MVC app and what I'm trying to do next is to start or submit a background task from my app.

Basically I'd like to keep the task going until it completes even if the user decides to do something else on the app.

But also I'd like to stop/kill/pause the task if I needed to. Since I haven't done this before I'm looking for a good/better way to do this.

I found these to be useful:

http://blog.springsource.com/2010/01/05/task-scheduling-simplifications-in-spring-3-0/

How do you kill a thread in Java?

Java threads: Is it possible view/pause/kill a particular thread from a different java program running on the same JVM?

So I wanted to use @Async task to submit my background task, but wanted to use threads' id to obtain it later on and stop it if needed?

Is this the right approach? I don't have any experience with multithreading so I'm here to listen.

Code update :

public interface Worker {
    public void work();
    public void cancel();
}

implementation :

@Component("asyncWorker")
public class AsyncWorker implements Worker {

    @Async
    public void work() {
        String threadName = Thread.currentThread().getName();
        System.out.println("   " + threadName + " beginning work");
        try {
                Thread.sleep(10000); // simulates work
        } catch (InterruptedException e) {
            System.out.println("I stopped");
        }
        System.out.println("   " + threadName + " completed work");
    }

    public void cancel() { Thread.currentThread().interrupt(); }
}

Controller for testing purposes :

@ResponseBody
@RequestMapping("/job/start")
public String start() {
    asyncWorker.work();
    return "start";
}

@ResponseBody
@RequestMapping("/job/stop")
public String stop() {
    asyncWorker.cancel();
    return "stop";
}

When I visit /job/start, I can't execute more that one task simultaneously. The other one starts to execute only after first one has completed

Also when I visit /job/stop the process isn't stopped, what am I missing here?

like image 318
Gandalf StormCrow Avatar asked Oct 01 '12 09:10

Gandalf StormCrow


1 Answers

Using thread ID is too low level and brittle. If you decided to use @Async annotation (good choice) you can use Future<T> to control the task execution. Basically your method should return a Future<T> instead of void:

@Async
public Future<Work> work() //...

Now you can cancel() that Future or wait for it to complete:

@ResponseBody
@RequestMapping("/job/start")
public String start() {
    Future<Work> future = asyncWorker.work();
    //store future somewhere
    return "start";
}

@ResponseBody
@RequestMapping("/job/stop")
public String stop() {
    future.cancel();
    return "stop";
}

The tricky part is to store the returned future object somehow so it is available for subsequent requests. Of course you cannot use a field or ThreadLocal. You can put in session, note however that Future is not serializable and won't work across clusters.

Since @Async is typically backed by thread pool, chances are your tasks didn't even started. Cancelling will simply remove it from the pool. If the task is already running, you can the isInterrupted() thread flag or handle InterruptedException to discover cancel() call.

like image 175
Tomasz Nurkiewicz Avatar answered Oct 21 '22 14:10

Tomasz Nurkiewicz