Представить текущий прогресс @Asynchronous функции для использования в представлении - PullRequest
1 голос
/ 28 июля 2010

В моем JEE6-приложении (работающем на Glassfish 3.0.1) у меня есть EmailEJB, который должен отправлять много писем. Письма отправляются асинхронно, поэтому они аннотируются новым EJB3.1 @Asynchronous, что позволяет запускать его в отдельном потоке. Теперь я хочу, чтобы пользователь был проинформирован о текущем состоянии метода: сколько писем уже отправлено?

Отправка писем асинхронно работает нормально, но я не могу понять, как сделать прогресс доступным извне. Похоже, мой подход сделать это совершенно неправильно, но каким-то образом это должно быть возможно (возможно, другой подход). Вот так выглядит мой EmailEJB в данный момент (это своего рода псевдокод, но объясняет, чего я хочу):

@Stateful
public class EmailEJB {

  @Asynchronous
  public Future<Integer> sendMails() {
    for (int i=0; i<mails.size; i++) {
      sendMail(mails[i])
      // i want to return the progress without returning ;)
      return new AsyncResult<Integer>(i)
    }
  }
}

//Just for the completeness... from outside, i'm accessing the progress like this:
Future<Integer> progress = emailEJB.sendEmails();
Integer currentvalue = progress.get();

Как я могу вернуть текущий прогресс внутри моей асинхронной функции, не отменяя его возвратом? Как я могу показать пользователю ход цикла внутри функции? Нужен ли мне другой асинхронный метод? Есть намеки?

Ответы [ 3 ]

4 голосов
/ 29 июля 2010

Nobody?Итак, это мое решение.Я не уверен, если это большой жирный обходной путь или просто способ сделать это.

Так как метод @Asynchronous не может получить доступ к контексту сеанса, и, следовательно, также нет сессионных компонентов (по крайней мере, я не знаю, как, я всегда получал ConcurrentModificationErrors или подобные), я создал Singleton ProgressEJB, который содержит HashMap:

@Singleton @LocalBean @Startup
public class ProgressEJB {
  private HashMap<String, Integer> progressMap = new HashMap<String, Integer>
  // getters and setters
}

Эта хэш-карта должна отображать SessionId (String) в целочисленное значение (прогресс 0-> 100).Таким образом, пользовательский сеанс связан с прогрессом.В моем EmailEJB я внедряю этот ProgressEJB, а в своем методе @Asynchronous я увеличиваю значение каждый раз, когда отправляется электронное письмо:

@Stateful @LocalBean
public class EmailEJB {
@Inject
private ProgressEJB progress;
// Mail-Settings
...
@Asynchronous
public void sendEmails(user:User, message:Message, sessionId:String) {
  progress.progressMap.put(sessionId, 0);
  for (int i=0; i<mails.size; i++) {
    sendMail(mails[i])
    progress.getProgressMap().put(sessionId, (i / mails.size) * 100)
  }
  progress.getProgressMap().remove(sessionId);
}

Идентификатор сеанса приходит из моего управляемого (Weld) компонентапри вызове функции:

@SessionScoped
@Named
public class EmailManager {
  @Inject 
  private ProgressEJB progress;
  @Inject
  private FacesContext facesContext;

  private String sessionId;

  @PostConstruct
  private void setSessionId() {
    this.sessionId = ((HttpSession)facesContext.getExternalContext().getSession(false)).getId();
  }

  public Integer getProgress() {
    if (progress.getProgressMap().get(sessionId) == null)
      return 100;
    else 
      return progress.getProgressMap().get(sessionId);
  }
}

Теперь я могу получить доступ к прогрессу из EmailManager из моего представления JSF с помощью Ajax Polling, сообщая пользователю, сколько писем уже отправлено.Только что проверил это с 2 пользователями, кажется, работает.

0 голосов
/ 23 октября 2017

Другое решение описано в Есть ли способ узнать ход асинхронного процесса EJB?

Это решение не нуждается в компоненте с сохранением состояния.

@Stateless
public class EmailEJB {
    // Mail-Settings
    ...
    @Asynchronous
    public void sendEmails(User user, Message message, WorkContext context) {
      progress.progressMap.put(sessionId, 0);
      for (int i=0; i<mails.size; i++) {
        sendMail(mails[i])
        context.setProgress((i / mails.size) * 100)
      }
      context.setRunning(false);
    }
}

Объект Context, который содержит прогресс.

public class WorkContext {
    //volatile is important!
    private volatile Integer progress = 0;
    private volatile boolean running = false;    
    // getters & setters
}

Использование очень просто.

@SessionScoped
@Named
public class EmailManager {
  @Inject 
  private EmailEJB emailEJB;
  private WorkContext workContext;

  public void doStuff() {
        workContext = new WorkContext();
        emailEJB.sendEmails(user, message, workContext)
  }

  public Integer getProgress() {
        return workContext.getProgress();
  }
  ....
}
0 голосов
/ 04 сентября 2013

Я также вижу только решение @Singleton здесь.Но это подразумевает необходимость ведения домашнего хозяйства в ProgressEJB.Например, необходимо приложить некоторые усилия, чтобы удалить старый сеанс из Hashmap.

...