Я использую 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
}
}
Для чего вам нужен вызываемый объект? Если вы хотите, чтобы он возвращал строку, просто верните ее String
из функции call
. Но кто называет Callable
?
@MatteoNNZ, API определил его и вызвал автоматически. Я буду получать данные и заполнять их в result
в цикле, но я не могу знать, когда будет достигнут конец, пока API не вызовет обратный вызов, чтобы сигнализировать об окончании задания, чтобы я мог выйти из цикла и вернуть данные.
@cyberbrain, я вызываю Callable из main, чтобы инициировать несколько вызываемых задач и ждать их возврата. Мне нужно, чтобы API сообщил мне, что все выполнено, прежде чем я узнаю, что потоки данных были получены, и он сообщает мне об этом, входя в этот обратный вызов void.
Вы можете использовать (атомарное) логическое значение, чтобы узнать, когда прекратить цикл:
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. Спасибо!
Думаю, volatile boolean
тоже подойдет.
@DidierL ты прав. Limestreetlab, если вы не знаете, Дидье прав, потому что переменная записывается одним потоком (обратным вызовом), поэтому нет необходимости быть атомарным. Но если производительность не поставлена на карту, оба варианта в порядке.
@MatteoNNZ, спасибо. Я только что принял ваше предложение и сейчас работаю. Спасибо за совет.
@DidierL, спасибо за предложение по CompletableFuture.
volatile boolean
является атомарным. Отличие от AtomicBoolean
заключается в том, что последний предоставляет дополнительные операции атомарного обновления, которые не используются в этом коде.
@Хольгер, да, летучего логического значения действительно достаточно, я соответствующим образом обновил код
Вы можете заставить это работать, добавив 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, но тот же принцип). Но я думаю, проблема в том, что вы не можете заблокировать вызов(), потому что он должен что-то делать до тех пор, пока не будет вызван обратный вызов.
Спасибо. Не знал о звонке CompletableFuture.complete(result)
.
@limestreetlab это название класса 😉
@MatteoNNZ да, эта часть вопроса не очень ясна
@DidierL, мало что читал о CompletableFuture, кажется, что новый класс ближе к Javascript Promise и CompletableFuture.complete
похож на Promise.resolve
@limestreetlab да… но учтите, что когда вы говорите «новее», на самом деле ему 10 лет 😏 Virtual Threads, вероятно, заменит большинство вариантов его использования сейчас.
Я думаю, что мы еще увидим завершаемые фьючерсы вместе с виртуальными потоками. Но с гораздо большим количеством простых join()
вызовов, а не длинных цепочек then…
операций.
@Holger, ну да… но тогда главное преимущество CompletableFuture
перед простым Future
заключается в том, что join()
не выбрасывает… OTOH ExecutorService.submit()
возвращает Future
и может иметь больше смысла в использовании, если вам не нужны функции complete()
и цепочки. Мне интересно посмотреть, что из всего этого получится :)
Возможно, вам все равно придется иметь дело с API, возвращающими CompletableFuture
, например java.lang.Process[Handle]
или HttpClient
. Иногда вы можете добавить один .thenAcceptAsync(…, Executors.newVirtualThreadPerTaskExecutor())
или аналогичный, чтобы гарантировать, что последующая операция выполняется в виртуальном потоке, независимо от того, как была реализована исходная операция, тогда последующая операция может использовать вызовы типа join()
и т. д.
Кто вызывает endOfJobCallback()? В своем комментарии вы говорите, что результат создается внутри call(), зачем вам «вернуть его внутри endOfJobCallback()»?