如何从同步方法调用中回调而不等待它?

Posted

技术标签:

【中文标题】如何从同步方法调用中回调而不等待它?【英文标题】:How to get called back from an sync method call without waiting for it? 【发布时间】:2021-07-20 14:49:31 【问题描述】:

我想在 EJB 类中编写一个方法,该类由于长时间运行而使用异步声明, 我不想等待结果,但我想获得有关完成或错误的通知。

类似这样的东西(仅限伪代码,因为不知道如何正确执行):

EJB / 区域设置异步 bean:

@LocaleBean
@Stateless
public AsyncEJBTest 
    @Asynchronous
    public Future<String> longRunningFunction(String name) 
        try 
            Thread.sleep(5000); // Simulate long running
         catch(Exception e) 
            //
        
        return new AsyncResult<String>(String.format("The result for %s has been calculated !", name));
    

CDI / 客户端 - 示例:

@Named
@ViewScoped
public class TestBean implements Serializable 
  private static final long serialVersionUID = 1L;
  @EJB
  private AsyncEJBTest test;

  public void StartTestClick() 
    Future<String> result = test.longRunningFunction("John doe");
    while(!result.isDone()) 
        System.out.println("Calculating..."); // Client is blocked here 
        Thread.sleep(300);
       
    // Continue with result, after long waiting ...
  

但正如我所看到的那样,我只需要在客户端/cdi 端等待结果的示例, 所以我的客户端被阻止了,因为我不使用异步标签。

那么当“计算”或任何事情已经完成时,还有另一种方法可以从企业 bean 中“通知”cdi-client 吗?

我想做的事情是通过克隆它们按需创建新数据库,通过以编程方式使用 cli/命令行界面自动配置具有新数据源的 Wildfly 服务器,并通知用户新数据库现在可用. 这需要很多时间,所以当我在客户端等待结果时我会超时 - 当我不需要结果时,异步似乎只在 EJB 端工作而不会阻塞......

【问题讨论】:

【参考方案1】:

一种方法是将Future&lt;String&gt; 句柄写入ApplicationScoped bean(可能是它们的列表)。然后在您的页面中有一个“检查”按钮,当您按下该按钮时,客户端应用程序会检查应用程序 bean 中列出的每个 Future&lt;String&gt; 句柄。如果作业现在已完成(或取消),它会从列表中删除句柄并显示一条消息。

如果这过于被动,请考虑在页面模板上添加一些内容或对每个页面导航进行检查。如果您想要即时弹出通知,请研究 JSF 2.3 Push 功能和 f:websocket。或者在异步作业结束时总是会生成一封无聊的旧电子邮件。您需要考虑如何收到通知,以及如果异步作业失败和/或作业完成后您不再使用应用程序或应用程序重新启动时会发生什么。

客户端视图

@Named
@ViewScoped
public class AsyncClientController implements java.io.Serializable 

    @Inject
    private FacesContext facesContext;
    @Inject
    private AsyncJobController jobController;
    @Inject
    private AsyncEJBStatelessBean asyncBean;

    public void startAsync() 
        String name = "Job " + 
            LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        Future<String> handle = asyncBean.longRunningJob(name);
        jobController.add(handle);
        facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Submitted", null));
    

    public void checkResults() 
        String res = jobController.getNewResults();
        if (res == null || res.isBlank()) 
            facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, 
                    "No new results", "Jobs logged: " + Integer.toString(jobController.size())));
         else 
            facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "New results", res));
        
    


应用程序 Bean

@Named
@ApplicationScoped
public class AsyncJobController implements java.io.Serializable 

    private final List<Future<String>> handlesList = new ArrayList<>();

    public void add(Future<String> handle) 
        handlesList.add(handle);
    

    public String getNewResults() 
        StringBuilder builder = new StringBuilder();
        List<Future<String>> finishedList = new ArrayList<>();

        handlesList.stream()
                .filter(handle -> (handle.isDone()))
                .forEachOrdered(handle -> 
                    finishedList.add(handle);
                );

        for (Future<String> handle : finishedList) 
            try 
                builder.append(builder.length() == 0 ? "" : "'\n'")
                        .append(handle.get());
             catch (InterruptedException | ExecutionException ex) 
                Logger.getLogger(AsyncJobController.class.getName()).log(Level.SEVERE, null, ex);
            
            handlesList.remove(handle);
        

        return builder.toString();
    

    public int size() 
        return handlesList.size();
    

异步无状态 EJB

@Stateless
@Asynchronous
public class AsyncEJBStatelessBean 
    
    @Inject
    private Logger log;
    
    public Future<String> longRunningJob(String name) 
        
        long sleepTime = (long) (Math.random()*60 + 0.5); // 1 .. 60
        log.log(Level.INFO, "Starting job ''0'' with duration 1", 
                new Object[]name, Long.toString(sleepTime));
        try 
            Thread.sleep(sleepTime * 1000);     // milliseconds
         catch (InterruptedException e) 
            // do nothing
        
        String res = "Job '" + name + "' finished after " + Long.toString(sleepTime) + " seconds";
        log.log(Level.INFO, "0", res);
        return new AsyncResult<>(res);
    
    

【讨论】:

以上是关于如何从同步方法调用中回调而不等待它?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用回调在任何函数中调用异步等待方法

同步调用,异步回调和 Future 模式

java中5种异步转同步方法

阿里P7大神:深入理解Java回调机制总结(同步回调/异步回调)

如何在.NET中调用异步函数而不等待它[重复]

Java回调机制总结