Не редкий случай встретить синхронный код там где его быть не должно. На этом маленьком примере я хочу показать как можно использовать корутины 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)


  1. shalomman
    03.01.2018 19:41

    А в чем преимущество корутин перед спринг вариантом с асинхронными методами и CompletableFuture?
    Например spring.io/guides/gs/async-method

    Как-то много плясок с бубном для асинхронного вызова.


    1. evkaky
      03.01.2018 20:19

      Очень развернутый ответ есть в докладе от одного из разработчиков:
      https://www.youtube.com/watch?v=HYhJmK9nKS4


      1. shalomman
        03.01.2018 20:32

        А есть простой и не развернутый? Я с радостью посмотрю часовое видео, но странно, что для очевидной проблемы нужно использовать неочевидное решение.


        1. evkaky
          03.01.2018 20:48

          Данная статья явно не вводная, из нее не понятно, чем корутины отличаются от CompletableFuture, RxJava и аннотации async из спринга, поэтому оно для вас выглядит как пляски с бубном.
          Короткий, не развернутый ответ: корутины позволяют писать асинхронный код так же, как и обычный синхронный, не вводя всё то множество «комбинаторов», которые нужны при программировании на CompletableFuture, RxJava итд.


    1. qwert_ukg Автор
      03.01.2018 20:21

      Им не нужен спринг, а вообще это дело вкуса.


      1. shalomman
        03.01.2018 20:36
        +1

        просто пример в статье на спринге и спринг стоит в тегах, а так, в джаве есть мрогочисленые решения асинхронных вызовов — начиная от обычных инструментов на уровне SDK, заканчивая библиотеками типа RxJava. Именно поэтому я бы предпочел увидеть преимущество использования не нативного решения.


        1. qwert_ukg Автор
          03.01.2018 20:59

          В сравнении с future/promise не нужно знать специальный метод из апи чтобы выполнить асинхронный вызов, например, в цикле — все также как с синхронным кодом. Вообще я видимо ошибся, именно с примером вызова из Java, и тем более с использованием Spring. Хотел лишь показать как можно пользоваться корутинами на примере.


  1. elegorod
    06.01.2018 15:46

    А есть возможность не писать Котлин-класс, а вызывать корутины напрямую из Java-кода? Что-то типа

    List<First> firstResult = new Coroutine().execute(() -> firstService.longCalculation())