Как Callable может вернуть значение из предопределенного обратного вызова void?

Я использую API-интерфейс, связанный с данными, который имеет функцию обратного вызова key void, которая автоматически вызывается, чтобы отметить окончание некоторых операций ввода-вывода. Я хочу сделать класс Callable<String> и использовать Future<String> result.

Я изо всех сил пытаюсь понять, как заставить Callable возвращать строку. Создание функции String returnResult(){return this.result} для вызова внутри не подходит.

Пожалуйста, порекомендуйте.

Это выглядит примерно так:

public class MyCallable implements someAPIWrapper, Callable<String> {
    
    String result;

    @Override
    public void endOfJobCallback() { //predefined API callback marking end of work
      /*
      usually read the data and write to a file, but not my case.
      how to return this.result string from here?
      */
    }

    @Override
    public String call() throws Exception {
      //some logic stuff
      //make API call to request a bunch of data
      //inside a loop to listen to incoming messages, receiving and appending to the *result* variable
      //end of all messages signalled by the ending callback, stop loop and return result var
    }

}

class Main {
    public static void main(String[] args){
      MyCallable callable = new MyCallable();
      ExecutorService executor = Executors.newFixedThreadPool(2);
      Future<String> future = executor.submit(callable);
      String result = future.get(); //blocking until result ready
    }
}

Кто вызывает endOfJobCallback()? В своем комментарии вы говорите, что результат создается внутри call(), зачем вам «вернуть его внутри endOfJobCallback()»?

Matteo NNZ 24.06.2024 22:04

Для чего вам нужен вызываемый объект? Если вы хотите, чтобы он возвращал строку, просто верните ее String из функции call. Но кто называет Callable?

cyberbrain 24.06.2024 22:04

@MatteoNNZ, API определил его и вызвал автоматически. Я буду получать данные и заполнять их в result в цикле, но я не могу знать, когда будет достигнут конец, пока API не вызовет обратный вызов, чтобы сигнализировать об окончании задания, чтобы я мог выйти из цикла и вернуть данные.

limestreetlab 24.06.2024 22:11

@cyberbrain, я вызываю Callable из main, чтобы инициировать несколько вызываемых задач и ждать их возврата. Мне нужно, чтобы API сообщил мне, что все выполнено, прежде чем я узнаю, что потоки данных были получены, и он сообщает мне об этом, входя в этот обратный вызов void.

limestreetlab 24.06.2024 22:19
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете использовать (атомарное) логическое значение, чтобы узнать, когда прекратить цикл:

public class MyCallable implements someAPIWrapper, Callable<String> {
    
    String result;
    private volatile boolean complete = false;

    @Override
    public void endOfJobCallback() { //predefined API callback marking end of work
      complete = true; //set Boolean to true atomically so that the other method will know you have been called
    }

    @Override
    public String call() throws Exception {
      while (!complete) {
        //Do your stuff
      }
      //if you reach this the callback was called
      return result;
    }

}

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

Я тоже решил использовать флаг. Установите для него значение true внутри этого обратного вызова, чтобы выйти из цикла !isDone. Спасибо!

limestreetlab 25.06.2024 01:46

Думаю, volatile boolean тоже подойдет.

Didier L 25.06.2024 01:52

@DidierL ты прав. Limestreetlab, если вы не знаете, Дидье прав, потому что переменная записывается одним потоком (обратным вызовом), поэтому нет необходимости быть атомарным. Но если производительность не поставлена ​​на карту, оба варианта в порядке.

Matteo NNZ 25.06.2024 09:56

@MatteoNNZ, спасибо. Я только что принял ваше предложение и сейчас работаю. Спасибо за совет.

limestreetlab 25.06.2024 10:32

@DidierL, спасибо за предложение по CompletableFuture.

limestreetlab 25.06.2024 10:32

volatile boolean является атомарным. Отличие от AtomicBoolean заключается в том, что последний предоставляет дополнительные операции атомарного обновления, которые не используются в этом коде.

Holger 26.06.2024 11:16

@Хольгер, да, летучего логического значения действительно достаточно, я соответствующим образом обновил код

Matteo NNZ 26.06.2024 11:23

Вы можете заставить это работать, добавив CompletableFuture в MyCallable и дополнив его endOfJobCallback().

public class MyCallable implements someAPIWrapper, Callable<String> {
    
    String result;
    CompletableFuture<String> future = new CompletableFuture<>();

    @Override
    public void endOfJobCallback() { //predefined API callback marking end of work
      future.complete(result);
    }
}

а затем в твоем main():

MyCallable callable = new MyCallable();
// do stuff with the API…

String result = callable.future.join(); //blocking until result ready

(конечно, для этого необходимо убедиться, что endOfJobCallback() вызывается в 100% случаев, в противном случае вам может потребоваться реализовать некоторую обработку ошибок или вы можете использовать get(timeout, unit) вместо join())

Я думал о чем-то подобном (на самом деле, используя CountDownLatch, но тот же принцип). Но я думаю, проблема в том, что вы не можете заблокировать вызов(), потому что он должен что-то делать до тех пор, пока не будет вызван обратный вызов.

Matteo NNZ 25.06.2024 09:57

Спасибо. Не знал о звонке CompletableFuture.complete(result).

limestreetlab 25.06.2024 10:38

@limestreetlab это название класса 😉

Didier L 25.06.2024 11:12

@MatteoNNZ да, эта часть вопроса не очень ясна

Didier L 25.06.2024 11:14

@DidierL, мало что читал о CompletableFuture, кажется, что новый класс ближе к Javascript Promise и CompletableFuture.complete похож на Promise.resolve

limestreetlab 25.06.2024 15:22

@limestreetlab да… но учтите, что когда вы говорите «новее», на самом деле ему 10 лет 😏 Virtual Threads, вероятно, заменит большинство вариантов его использования сейчас.

Didier L 25.06.2024 17:43

Я думаю, что мы еще увидим завершаемые фьючерсы вместе с виртуальными потоками. Но с гораздо большим количеством простых join() вызовов, а не длинных цепочек then… операций.

Holger 26.06.2024 11:19

@Holger, ну да… но тогда главное преимущество CompletableFuture перед простым Future заключается в том, что join() не выбрасывает… OTOH ExecutorService.submit() возвращает Future и может иметь больше смысла в использовании, если вам не нужны функции complete() и цепочки. Мне интересно посмотреть, что из всего этого получится :)

Didier L 26.06.2024 13:30

Возможно, вам все равно придется иметь дело с API, возвращающими CompletableFuture, например java.lang.Process[Handle] или HttpClient. Иногда вы можете добавить один .thenAcceptAsync(…, Executors.newVirtualThreadPerTaskExecutor()) или аналогичный, чтобы гарантировать, что последующая операция выполняется в виртуальном потоке, независимо от того, как была реализована исходная операция, тогда последующая операция может использовать вызовы типа join() и т. д.

Holger 26.06.2024 19:17

Другие вопросы по теме