13

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?

Community
  • 1
  • 1
Gandalf StormCrow
  • 25,788
  • 70
  • 174
  • 263

1 Answers1

11

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.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • +1 for @Async. Can you add a link to http://static.springsource.org/spring/docs/3.0.x/reference/scheduling.html? – David Grant Oct 01 '12 at 09:43
  • @Tomasz Nurkiewicz hello thank you for the response. The class actually containing implementation of `fooAsync()` does it need to implement Runnable? if I were to create a cancel method which interrupts thread performing background task, will the Thread.currentThread().interrupt interrupt that particular thread? Can you give an example please? – Gandalf StormCrow Oct 01 '12 at 10:30
  • @DavidGrant: sure, also you are free to edit any post on SO (which you already did, thanks). – Tomasz Nurkiewicz Oct 01 '12 at 11:10
  • @GandalfStormCrow: no, that's the beauty, Spring will automatically wrap your method annotated with `@Async` in a runnable proxy. No further changes are required. Check the [documentation](http://static.springsource.org/spring/docs/current/spring-framework-reference/html/scheduling.html#scheduling-annotation-support-async). I clarified my post a bit. Also again: `Future.cancel()` does not interrupt any custom thread. It just interrupts this particular `Future` object, the backing thread pool keeps working. Do not create thread per request. – Tomasz Nurkiewicz Oct 01 '12 at 11:12
  • @TomaszNurkiewicz I know, but it was quite fundamental, so I wanted to leave it to you ;) – David Grant Oct 01 '12 at 11:31