31

我在 Spring 服务中定义了一个长期运行的任务。它由 Spring MVC 控制器启动。我想在服务结束之前启动服务并返回HttpResponse给调用者。该服务最后将文件保存在文件系统上。在 javascript 中,我创建了一个轮询作业来检查服务状态。

在 Spring 3.2 中,我找到了@Async注释,但我不明白它与DeferredResultand有何不同Callable。我什么时候必须使用@Async,什么时候应该使用DeferredResult

4

3 回答 3

24

Async注释一个方法,因此它将被异步调用。

@org.springframework.stereotype.Service
public class MyService {
    @org.springframework.scheduling.annotation.Async
    void DoSomeWork(String url) {
        [...]
    }
}

所以 Spring 可以这样做,因此您需要定义如何执行。例如:

<task:annotation-driven />
<task:executor id="executor" pool-size="5-10" queue-capacity="100"/>

这样,当您调用 service.DoSomeWork("parameter") 时,调用将被放入执行程序的队列中以异步调用。这对于可以同时执行的任务很有用。

您可以使用 Async 执行任何类型的异步任务。如果您想要定期调用任务,您可以使用@Scheduled(并使用 task:scheduler 而不是 task:executor)。它们是调用 java Runnables 的简化方法。

DeferredResult<>用于在不阻塞用于回答的 Tomcat HTTP 线程的情况下回答请求。通常将是 ResponseBody 注释方法的返回值。

@org.springframework.stereotype.Controller
{
    private final java.util.concurrent.LinkedBlockingQueue<DeferredResult<String>> suspendedRequests = new java.util.concurrent.LinkedBlockingQueue<>();

    @RequestMapping(value = "/getValue")
    @ResponseBody
    DeferredResult<String> getValue() {
            final DeferredResult<String> result = new DeferredResult<>(null, null);
            this.suspendedRequests.add(result);
            result.onCompletion(new Runnable() {
            @Override
            public void run() {
        suspendedRequests.remove(result);
            }
});
            service.setValue(result); // Sets the value!
            return result;
    }
}

前面的示例缺少一件重要的事情,那就是它没有显示延迟结果将如何设置。在其他一些方法(可能是 setValue 方法)中,将会有一个 result.setResult(value)。在调用 setResult 之后,Spring 将调用 onCompletion 过程并将答案返回给 HTTP 请求(参见https://en.wikipedia.org/wiki/Push_technology#Long_polling)。

但是,如果您只是同步执行 setValue,则使用延迟结果没有任何优势。这就是 Async 的用武之地。您可以使用异步方法在将来的某个时间使用另一个线程设置返回值。

    @org.springframework.scheduling.annotation.Async
    void SetValue(DeferredResult<String> result) {
        String value;
        // Do some time consuming actions
        [...]
        result.setResult(value);
    }

使用延迟结果不需要异步,它只是一种方法。

在示例中,有一个延迟结果队列,例如,一个计划任务可能正在监视以处理它的未决请求。您也可以使用一些非阻塞机制(参见http://en.wikipedia.org/wiki/New_I/O)来设置返回值。

要完成图片,您可以搜索有关 java 标准期货 ( http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Future.html ) 和可调用对象 ( http:// docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Callable.html)在某种程度上相当于 Spring DeferredResult 和 Async。

于 2013-08-08T08:24:23.723 回答
24

您的控制器最终是一个由 servlet 容器(我假设它是 Tomcat)工作线程执行的函数。您的服务流程以 Tomcat 开始,以 Tomcat 结束。Tomcat从客户端获取请求,持有连接,最终返回响应给客户端。您的代码(控制器或 servlet)位于中间的某个位置。

考虑这个流程:

  1. Tomcat 获取客户端请求。
  2. Tomcat 执行你的控制器。
  3. 释放 Tomcat 线程但保持客户端连接(不返回响应)并在不同线程上运行繁重的处理。
  4. 当您的繁重处理完成后,使用其响应更新 Tomcat 并将其返回给客户端(通过 Tomcat)。

因为 servlet(你的代码)和 servlet 容器(Tomcat)是不同的实体,所以为了允许这个流程(释放 tomcat 线程但保持客户端连接),我们需要在他们的合同中提供这种支持,包,在Servletjavax.servlet中引入3.0 . 现在,回到您的问题,当控制器的返回值为DeferredResultor时,Spring MVC 使用新的 Servlet 3.0 功能Callable,尽管它们是两个不同的东西。Callable是 的一部分的接口java.util,它是对接口的改进Runnable(应该由其实例打算由线程执行的任何类实现)。Callable允许返回一个值,而Runnable不允许。DeferredResult是 Spring 设计的一个,它允许更多选项(我将描述)用于 Spring MVC 中的异步请求处理,并且这个类只保存结果(如其名称所暗示的那样),而您的Callable实现保存异步代码。所以这意味着你可以在你的控制器中使用这两者,运行你的异步代码Callable并将结果设置在 中DeferredResult,这将是控制器的返回值。那么通过使用DeferredResult作为返回值而不是 Callable 会得到什么?DeferredResult具有内置回调,如onError,onTimeoutonCompletion. 它使错误处理变得非常容易。此外,由于它只是结果容器,您可以选择任何线程(或线程池)在您的异步代码上运行。使用 Callable,您没有这个选择。

关于@Async,它要简单得多——用 注释 bean 的方法@Async将使其在单独的线程中执行。默认情况下(可以重写),Spring 使用 aSimpleAsyncTaskExecutor来实际异步运行这些方法。

总之,如果您想在执行繁重处理时释放 Tomcat 线程并保持与客户端的连接,那么您的控制器应该返回Callableor DeferredResult。否则,您可以在带有注释的方法上运行代码@Async

于 2018-07-27T18:26:03.623 回答
5

DeferredResult利用 Servlet 3.0 AsyncContext。当您需要返回结果时,它不会像其他线程那样阻塞线程。

另一个很大的好处是DeferredResult支持回调。

于 2013-07-25T11:59:58.550 回答