Не редкий случай встретить синхронный код там где его быть не должно. На этом маленьком примере я хочу показать как можно использовать корутины Kotlin в Java
Будем использовать IntelliJ IDEA и Maven
Допустим у нас есть синхронный код на Java (надеюсь я выбрал популярный юзкейс с кодом Java):
@GET
@Path("/somethings")
public Result getResultOfSomething() {
List<First> firstResult = firstService.longCalculation(); // долгое вычисление
List<Second> secondResult = secondService.veryLongCalculation(); // очень долгое вычисление
List<Third> thirdResult = thirdService.longestCalculation(); // максимально долгое вычисление
return new Result(firstResult, secondResult, thirdResult); // возвращаем результирующий объект
}
Далее добавим Kotlin и поддержку карутин в наш pom.xml
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>0.21</version>
</dependency>
Чтобы все собиралось поправим содержимое тега <build>
во соответствии с описанием
Также добавим директорию для исходников Kotlin в проект и создадим там Kotlin класс KotlinServiceAsync
в неймспесе services
со следующим содержимым:
class KotlinServiceAsync(private val firstService: FirstService, private val secondService: SecondService, private val thirdService: ThirdService) { // передаем необходимые сервисы в качестве параметров главного конструктора (можно делать через DI)
// функциональная запись возвращающая билдер [runBlocking] который заблокирует главный поток, пока корутины внутри не будут выполнены
fun getResultOfSomethingAsync() = runBlocking { // тип будет выведен компилятором в соответствии с типом последнего выражения внутри билдера
val firstResult = async { // запускаем корутину в отдельном потоке и сохраняем ссылку в переменную
firstService.longCalculation() // Этот код будет запущен в отдельном потоке, также последнее выражение будет возвращено неявно
}
val secondResult = async {
secondService.veryLongCalculation()
}
val thirdResult = async {
thirdService.longestCalculation()
}
Result(firstResult.await(), secondResult.await(), thirdResult.await()) // ждем пока все корутины выполнится и неявно возвращаем результат
}
}
Теперь вызываем метод getResultOfSomethingAsync
из Java:
@GET
@Path("/somethings")
public Result getResultOfSomething() {
return new KotlinServiceAsync(firstService, secondService, thirdService).getResultOfSomethingAsync()
}
Все корутины по умолчанию выполняются в CommonPool
, чтобы создать отдельный пул можно использовать функцию newFixedThreadPoolContext
:
val myPool = newFixedThreadPoolContext(8, "myPool") // все ресурсы будут освобождены после того как программа отработает
И передать контекст корутин в качестве параметра билдера async
:
val firstResult = async(myPool) {
firstService.longCalculation()
}
Чтобы постоянно не передавать контекст можно создать свой билдер на основе async
:
fun <T> myAsync(block: suspend CoroutineScope.() -> T) = async(myPool, block = block)
Вот и все
P.S
Туториал лишь описывает один из многих вариантов реализации подобных задач и не претендует вообще ни на что. Любая критика только приветствуется, буду рад узнать где я ошибся.
Всем спасибо!
Комментарии (8)
elegorod
06.01.2018 15:46А есть возможность не писать Котлин-класс, а вызывать корутины напрямую из Java-кода? Что-то типа
List<First> firstResult = new Coroutine().execute(() -> firstService.longCalculation())
shalomman
А в чем преимущество корутин перед спринг вариантом с асинхронными методами и CompletableFuture?
Например spring.io/guides/gs/async-method
Как-то много плясок с бубном для асинхронного вызова.
evkaky
Очень развернутый ответ есть в докладе от одного из разработчиков:
https://www.youtube.com/watch?v=HYhJmK9nKS4
shalomman
А есть простой и не развернутый? Я с радостью посмотрю часовое видео, но странно, что для очевидной проблемы нужно использовать неочевидное решение.
evkaky
Данная статья явно не вводная, из нее не понятно, чем корутины отличаются от CompletableFuture, RxJava и аннотации async из спринга, поэтому оно для вас выглядит как пляски с бубном.
Короткий, не развернутый ответ: корутины позволяют писать асинхронный код так же, как и обычный синхронный, не вводя всё то множество «комбинаторов», которые нужны при программировании на CompletableFuture, RxJava итд.
qwert_ukg Автор
Им не нужен спринг, а вообще это дело вкуса.
shalomman
просто пример в статье на спринге и спринг стоит в тегах, а так, в джаве есть мрогочисленые решения асинхронных вызовов — начиная от обычных инструментов на уровне SDK, заканчивая библиотеками типа RxJava. Именно поэтому я бы предпочел увидеть преимущество использования не нативного решения.
qwert_ukg Автор
В сравнении с future/promise не нужно знать специальный метод из апи чтобы выполнить асинхронный вызов, например, в цикле — все также как с синхронным кодом. Вообще я видимо ошибся, именно с примером вызова из Java, и тем более с использованием Spring. Хотел лишь показать как можно пользоваться корутинами на примере.