#scala #concurrency #promise #future
#scala #параллелизм #обещание #будущее
Вопрос:
Имеет ли смысл иметь подобную операцию getOrElseComplete
, которая пытается завершить a Promise
значением, но, если Promise
оно уже завершено, вместо этого возвращает существующее завершенное значение. Вот пример реализации:
implicit class PromiseOps[T](promise: Promise[T]) {
def getOrElseComplete(value: Try[T]): Try[T] = {
val didComplete = promise.tryComplete(value)
if (didComplete) {
value
} else {
// The `tryComplete` returned `false`, which means the promise
// was already completed, so the following is safe (?)
promise.future.value.get
}
}
}
Безопасно ли это делать? Если нет, то почему бы и нет? Если да, есть ли способ сделать это напрямую (например, не полагаясь на сомнительные вещи, такие как _.value.get
) или почему в стандартной библиотеке нет такого способа?
Комментарии:
1. Зачем вам это нужно? Похоже на проблему XY
2. @ghik Я хочу принять одно атомарное решение, и есть несколько разных потоков, которые могут принимать это решение (по-разному) в зависимости от того, что происходит в системе. Затем я также хочу иметь возможность наблюдать за этим решением для завершения. Я думаю, я мог бы сделать это с помощью an
AtomicReference
и aPromise
, но, учитывая, что aPromise
уже обертывает anAtomicReference
(чтобы быть потокобезопасным), кажется, проще сделать все это с одним объектом promise (меньше внешних инвариантов и меньше кода).3. Нет причин использовать
Promise
, если вы не передаете егоFuture
кому-то. Ваш код не показывает, как вы это раскрываете,Future
поэтому трудно сказать, как правильно это сделать. Это конкретное решение кажется странным, потомуPromise
что должно быть только настраиваемым «бэкэндом» aFuture
, и вы не должны использоватьPromise
само по себе в качестве источника результирующего значения.4. Я отдаю его
Future
кому-то. Вот как выполняется часть «просмотр этого решения для завершения». Я хочу как следить за завершением, так и иногда сразу локально знать, каким было это решение.
Ответ №1:
Из ваших комментариев мне кажется, что это правильное решение вашей проблемы, но я также считаю, что подобный метод не относится к Promise
API, потому Promise
что предполагается, что он является только настраиваемым «бэкэндом» Future
.
Я бы предпочел иметь совершенно отдельную функцию, которая могла бы выглядеть так:
def getOrComplete[T](promise: Promise[T], value: Try[T]): Try[T] =
promise.future.value.getOrElse {
if (promise.tryComplete(value)) value
else getOrComplete(promise, value)
}
Рекурсивный вызов может показаться странным — он служит двум целям:
- это защищает от состояния гонки, когда какой-то другой поток завершает future непосредственно перед вызовом
tryComplete
- это позволяет избежать использования
.value.get
наFuture
Возможно, вы также захотите передать value
его в качестве параметра по имени, чтобы избежать его оценки, когда Promise
он уже завершен.
Комментарии:
1. Очень креативный способ избавиться от ‘get’! Интересно, как заканчивается производительность, учитывая, что даже на счастливом пути вы в конечном итоге делаете два вызова.
2. Если вы действительно хотите избавиться от второго вызова, тогда используйте сопоставление с образцом вместо
getOrElse
и вызов становится хвостовым рекурсивным, что означает, что он эффективно устраняется в байт-коде.3. Да, это было бы действительно лучше — спасибо, что указали, что вызов может быть хвостовым рекурсивным.
4. @Alec, будущее из Promise — это тот же объект с другим интерфейсом, поэтому вызов
future
бесплатный. Я не помню, как это защищено, но, вероятно, с помощью дешевого неоспоримого чтения (я бы предположил, что оно прочитано, если Some (x) возвращает, иначе получает блокировку, чтение, возврат)
Ответ №2:
Эта операция выполняет то, что обещает. Возможно, имеет смысл брать value
по имени и не пытаться завершить, если уже завершено, может быть, что-то вроде
def getOrElseComplete(value: => Try[T]): Try[T] = {
if (!promise.completed) {
promise.tryComplete(value)
}
promise.future.value.get
}
Хотя это довольно хитро. Совместное использование обещания и наличие нескольких мест, где оно может быть завершено, звучит как сложный в обслуживании дизайн, и нужно спросить, что происходит с другим путем, который все еще может выполнить обещание? Разве там что-то не должно быть отменено?
Комментарии:
1. Обещание представляет результат принятия решения, и может быть несколько параллельных путей кода, которые участвуют в гонке для завершения
Promise
. Пути к коду могут делать что-то вродеtryComplete
и использовать результат этого, чтобы атомарно попытаться принять «решение», одновременно проверяя, попал ли туда какой-то другой поток первым. В другом месте я могу посмотреть обещанияFuture
и запланировать некоторую работу, как только решение будет принято.2. Передача
value
по имени означает, что иногдаvalue
может оцениваться, даже если оно не является значением завершения обещания (например. при выполнении двух вызововgetOrElseComplete
). Само по себе это не проблема, но о ней стоит знать.