От переводчика

Это мой первый перевод, поэтому прошу прощения за неточности. Если вы найдете ошибки в переводе, пожалуйста, сообщите об этом. Я не нашел лучшего перевода слова сoroutine, чем сопрограмма, поэтому решил использовать оригинал. Если у вас появятся идеи по этому поводу, буду рад узнать.


Kotlin версии 1.1 принесет в язык coroutin'ы, которые позволяют приостанавливать вычисления в какой-то точке, а затем продолжить их позднее. Очевидный пример этой возможности — async-await, который был добавлен в C# несколько лет назад.


Каждый android разработчик знает, что когда мы имеем дело с сетевыми запросами или другими I/O задачами, то нам необходимо удостовериться, что не происходит блокировка основного потока, а так же, что мы не трогаем UI из фонового потока. На протяжении многих лет приходят и уходят десятки приемов. В этой статье перечислены наиболее популярные, и показаны примеры удобства, которое несет с собой async-await.


Сценарий


Мы хотим получить данные пользователя Github и положить их в базу данных, а после показать результат на экране. Я не стал объяснять подходы, они скажут всё сами за себя.


Старый добрый Thread


Ручное управление, полный контроль


fun threads() {
  val handler = Handler()

  Thread {
    try {
      val user = githubApi.user()
      userRepository.store(user)
      handler.post {
        threadsTV.text = "threads: [$user]"
      }
    } catch(e: IOException) {
      handler.post {
        threadsTV.text = "threads: [User retrieval failed.]"
      }
    }
  }.start()
}

AsyncTask андроида


Никто же их не использует больше, верно?


fun asyncTask() {
  object : AsyncTask<Unit, Unit, GithubUser?>() {

    private var exception: IOException? = null

    override fun doInBackground(vararg params: Unit): GithubUser? {
      try {
        val user = githubApi.user()
        userRepository.store(user)
        return user
      } catch(e: IOException) {
        exception = e
        return null
      }
    }

    override fun onPostExecute(user: GithubUser?) {
      if (user != null) {
        asyncTaskTV.text = "asyncTask: [$user]"
      } else {
        asyncTaskTV.text = "asyncTask: [User retrieval failed.]"
      }
    }
  }.execute()
}

Callbacks


А Callback-hell кто-ниубдь использует?


fun callbacks() {
  githubApi.userFromCall().enqueue(object : Callback<GithubUser> {

    override fun onResponse(call: Call<GithubUser>, response: Response<GithubUser>) {
      val user = response.body()
      userRepository.storeCallback(user) {
        callbacksTV.text = "callbacks: [$user]"
      }
    }

    override fun onFailure(call: Call<GithubUser>, t: Throwable) {
      if (t is IOException)
        callbacksTV.text = "callbacks: [User retrieval failed.]"
      else
        throw t
    }
  })
}

Rx


Предоставляет крутые вещи...


fun rx() {
  githubApi.userRx()
      .flatMap { user ->
        userRepository.storeRx(user).toSingle { user }
      }
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(
          { user ->
            rxTV.text = "rx: [$user]"
          },
          { throwable ->
            if (throwable is IOException)
              rxTV.text = "rx: [User retrieval failed.]"
            else
              throw throwable
          }
      )
}

Async-Await


А как вы смотрите на это?


fun asyncAwait() = asyncUI {
  try {
    val user = await(githubApi.userAsync())
    await(userRepository.storeAsync(user))
    asyncAwaitTV.text = "asyncAwait: [$user]"
  } catch(e: IOException) {
    asyncAwaitTV.text = "asyncAwait: [User retrieval failed.]"
  }
}

Тут asyncUI (и аналогичный async<Т>) метод включет функционал coroutin'ы, который предоставляет доступ к методу await. Каждый раз, когда выполнение достигает метода await, вычисления приостанавливаются до тех пор, пока параметр не будет вычислен, но поток, в котором произошел вызов, не блокируется. После этого coroutine продолжит свое выполнение. Метод asyncUI гарантирует, что выполнение продолжится в главном потоке.




Как вы заметили, coroutine повышает читаемость кода. Они доступны уже сейчас в версии kotlin 1.1-M02. Последний пример async-await использует библиотеку, которую я написал для возможности использования coroutines на Android. Если хотите больше узнать о coroutin'ах, то можете ознакомиться с неформальным описанием


PS: Эта статья не содержит отмены выполнений и удаления слушателей, которые могут содержать ссылки на активити. Каждый подход может иметь схожий вариант, но без утечек.


В следующей статье я сделал более детальный разбор async-await.

Поделиться с друзьями
-->

Комментарии (29)


  1. vintage
    08.11.2016 09:36
    -4

    Тут разбираются различные модели однопотоxой многозадачности на примере NodeJS, но те же выводы применимы для любой платформы. async functions, generators, promises, callbacks — теряют стек вызова, из-за чего их сложнее отлаживать, имеют пенальти по производительности и требуют переписывания всего кода приложения "в своём стиле".


    1. andreich
      08.11.2016 10:28

      Про остальное не скажу, но вот на счет колбеков и async-await, то в котлине стектрейс сохраняется. Да и вообще, без колбеков в андроид разработке сложновато будет жить. Что касается производительности, то за многие вкусности приходится платить. Обычно тут вопрос стоит ли оно того или нет.


      1. vintage
        08.11.2016 10:58
        +1

        Каким образом он может сохраняться? Дампится в момент создания замыкания? Тогда это ещё более медленно. Не набросаете пример?


        1. andreich
          08.11.2016 11:58

          Вообще в java, а котлин больше в эту сторону, можно получить стектрейс текущего потока как-то так

          Thread.currentThread().getStackTrace().
          


          1. vintage
            08.11.2016 12:15

            Получить-то его везде можно. Он автоматически приклеивается после возвращаения в сопрограмму? Кстати, "сопрограмма" — вполне устоявшийся термин, зря вы его не используете.


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


            1. fogone
              08.11.2016 12:41
              -1

              Да, там будет честный стректрейс, но другого и ожидать глупо. Или есть какие-то более хорошие идеи, что за стректрейс там должен быть?


              1. vintage
                08.11.2016 12:47

                Опять же, вместо того, чтобы "минусовать рекламу" вы бы лучше почитали. Я зря старался что ли? Там есть примеры:


                Честный стектрейс:


                TypeError: Cannot read property 'name' of null
                    at Object.<anonymous> (./user.js:18:33)
                    at next (native)
                    at onFulfilled (./node_modules/co/index.js:65:19)

                Полезный стектрейс:


                TypeError: Cannot read property 'name' of null
                    at Object.module.exports.getName (./user.js:14:23)
                    at Object.module.exports.say (./greeter.js:2:41)
                    at Future.task.error (./index.js:11:17)
                    at ./node_modules/fibers/future.js:467:21


                1. fogone
                  08.11.2016 13:09

                  Это же абсолютно разные вещи. Для «fibers» есть решения типа quasar, который по своему неплох, но требует инструментирования байткода и решает свои специфичные проблемы. В котлине же на уровне языка сделали корутины, которые лишь позволяют передавать управление определенным образом. Так вот, если мы передали управление, то нужно ожидать, что стектрейс будет согласован с тем как мы передаем управление. И я не думаю, что можно придумать более удачный стектрейс для такого решения.


                  1. vintage
                    08.11.2016 13:24

                    А какие там проблемы?


                    Не очень понял какой стектрейс вы называете "согласованным с тем, как передаём управление". Который от старта приложения или который от асинхронного события?


                    1. fogone
                      08.11.2016 14:23
                      -1

                      А какие там проблемы?

                      Выполнение кода в нитях.
                      Который от старта приложения или который от асинхронного события?

                      Еще раз: никакой асинхронности в корутинах нет. Это только способ передачи управления. Компилятор компилирует корутину специальным образом, чтобы можно было передать управление в ключевые точки функции. Если внутри корутины запустить поток (например), то конечно можно сделать асинхронный вызов типа async/await, но в своей сути это просто передача управления. Можно реализовать, например, генерацию последовательностей аля-yield, которая никак с асинхронностью не связана. И с этой точки зрения стектрейс это последовательность передачи управления. Стектрейс это всегда активный стек выполняющегося потока, поэтому даже если мы перешли обратно в корутину, но уже в другом потоке, то стек будет конечно текущего потока, потому что так работает jvm и именно это ожидает увидеть разработчик.


                      1. vintage
                        08.11.2016 14:54
                        +1

                        Любой код выполняется в нитях. Так в чём проблемы fibers?


                        Опять вы выдали пространную тираду из которой мне приходится догадываться какой вариант "вы ожидаете увидеть". Вы можете ответить прямо — первый или второй? А лучше приведите пример кода и стектрейса.


                        1. fogone
                          08.11.2016 15:42

                          Любой код выполняется в нитях. Так в чём проблемы fibers?

                          Тут путаница некоторая в терминологии произошла. Когда я говорил «нити», я имел ввиду легковесные потоки (fibers). Так вот выполнение кода в легковесном потоке требует достаточно большой работы по управлению этой инфраструктурой, почитайте хотя бы о том же quasar-е, которая пытается реализовать модель акторов из эрланга. Конечно, если выполнять всё в одном потоке, как это делает нода, то проблем минимум, но есть и другие языки, которые пытаются утилизировать все ядра или даже работать распределенно. И для таких решений уже легковесные нити работающие все в одном потоке не подойдут, там делается система акторов, которая обменивается сообщениями через очереди, имеет достаточно сложное управление потоками, инструментирует байткод для создания корутин и имеет ряд ограничений — всё это не подойдет для каждого первого приложения. Поэтому в котлине сделали из коробки корутины, на базе которых можно делать те решения, которые нужно.

                          Вы можете ответить прямо — первый или второй?

                          Первый. Второй вариант в jvm просто невозможен, просто потому что выполнение кода в каком-то потоке будет всегда иметь стектрейс этого потока. Хотя, конечно, всегда можно сделать искусственный стектрейс. Например для дебага — просто сохранить стек точки вызова, но это вряд ли подойдет для прода высокопроизводительных систем. В котлине это можно сделать — для этого есть инструментарий языка.


                          1. vintage
                            08.11.2016 21:24
                            +1

                            Я читал как это реализовано в Go и реализовывал в D. Не заметил там особых сложностей. Возможно проблема именно в JVM, который не даёт полного контроля. Но неужели никто не запилил какого-нибудь JNI расширения, как это сделали для NodeJS?


                            Вообще говоря, есть два перпендикулярных понятия: concurency и parallelism. так вот, раскидывание задач по ядрам — это paralellism. А выполнение нескольких задач на одном ядре — concurrency. У вас может быть одно, но не быть другого и наоборот. Например, в D fibers реализуют исключительно concurency — поток крутится в event-loop-е и выполняет задачи, беря их из очереди. А вот paralellism реализуется с помощью threads — вы можете одну задачу запустить на множестве потоков. В частности, вы можете запустить задачу "крутить event-loop" на "thread-pooll-е", который по умолчанию создаёт число "worker-thread" на 1 меньше числа ядер. Стандартный механизм коммуникации через посылку сообщений не очень удобен, поэтому я и реализовал "wait-free channels" — с ними производить синхронизацию задач — одно удовольствие. Правда типобезопасность не докрутил ещё.


                            Наоборот, я так понял первый ("от старта приложения до точки вызова функции") в JVM и невозможен, что очень печально. Переключение fibers, вообще говоря — плёвая операция — просто меняется указатель на стек и fiber просыпается, словно и не засыпал. Когда fiber решает, что ему делать нечего, то вызывает специальную функцию, которая находит следующую задачу на исполнение и передаёт управление ей или же сам напрямую передаёт ей управление, если он на ней "блокируется". А поскольку для этого даже не нужно переходить в режим ядра, то пенальти тут совсем незначительное. По идее, даже если в JVM такое не поддерживает — это должно быть не сложно прикрутить через нативный интерфейс.


                            1. fogone
                              09.11.2016 12:52

                              Возможно проблема именно в JVM, который не даёт полного контроля.

                              Я же выше присылал ссылку на реализацию легковесных потоков для jvm. Сложности как обычно в деталях.
                              А выполнение нескольких задач на одном ядре — concurrency.

                              Как раз всё примерно наоборот: параллелизм можно без особых проблем реализовать в одном потоке при помощи тех же легковесных потоков, а вот канкаренси это именно о доступе к одним данным из разных потоков (даже не обязательно одной физической машины), потому что никакой конкуренции в доступе к одним данным из одного и того же потока нет, потому что всё выполняется последовательно. Такие языки как javascript или питон используют такой способ синхронизации — но это не столько канкаренси, сколько способ его избежать.

                              Наоборот, я так понял первый («от старта приложения до точки вызова функции») в JVM и невозможен, что очень печально.

                              Первый стектрейс был тот, что назван «честным» — так вот именно он будет. Тот, что назван «полезным» его можно несложно получить вручную при реализации того же async/await, но не думаю, что это хорошая идея для производительных систем, потому что каждый раз, когда мы будем передавать управление, нам придется строить «полезный» стектрейс, что может сказаться на производительности — хотя не буду наверняка это утверждать.
                              Переключение fibers, вообще говоря — плёвая операция — просто меняется указатель на стек и fiber просыпается, словно и не засыпал.

                              Ну да, это просто последовательная передача управления в это место, чего тут долгого? К чему вы это?
                              По идее, даже если в JVM такое не поддерживает — это должно быть не сложно прикрутить через нативный интерфейс.

                              JVM-у всё равно, он будет выполнять тот байткод, который ему дадут. Для реализации лековесных потоков просто нужна возможность этот байткод сделать. Чтобы сделать полноценные fibers quasar очень серьезно инструментирует обычный последовательный код, что приводит к «особенностям» его использования, потому что не любой код можно таким образом преобразовать. Котлин же предлагает способ создавать подобный код с помощью корутин. Это не позволяет перекраить весь байткод под свои нужды, но позволяет возвращаться к выполнению какого-то метода — что по сути и делают леговесные потоки. Ведь async/await это такая простая реализация того же подхода. Да, вероятно в коде на котлине придется «помечать» какой именно код требует саспенда, но это даже может и не плохо, здесь мы хотя бы обходимся без уличной магии, которая в сложных случаях или не работает или дает непонятную логику. На базе того инструментария, который сейчас есть в котлине, можно реализовать код, который мы внизу смотрели:

                              fun greeting() = async {
                              
                                  val config by httpResourceJson("./config.json")
                                  // тут запустился первый асинхронный запрос.
                              
                                  val profile by httpResourceJson("./profile.json")
                                  // тут запустился второй асинхронный запрос.
                              
                                  val greeting = config.replace("{name}", profile)
                                  // а вот тут данные ответа потребовались и произошла остановка задачи
                              
                                  // сюда управление уже дойдёт лишь, когда все асинхронные запросы отработают.
                                  return greeting
                              }
                              

                              Сделав кастомизацию только для CompletableFuture как local delegates — при этом всё будет работать точно так как вы этого ожидаете — т.е. в этом методе поток будет находиться только когда для него есть дело. А если так, то нужно ли сложное решение с большим числом возможных проблем из за обобщенности решения, если частное решение работает не хуже и из коробки без дополнительного инструментирования байткода?


                              1. vintage
                                09.11.2016 13:26

                                Нет, concurency — в общем случае это конкуренция за какой-либо ресурс. Применительно же к многозадачности, это этот ресурс — вычислительное ядро. Множество задач конкурируют за право владеть ядром какое-то время.


                                parallellism — это именно про одновременное исполнение. Так как каждое ядро может исполнять лишь одну задачу за раз (в случае гипертрейдинга это справедливо для логического ядра, но не физического), то одновременное исполнение возможно только на разных ядрах.


                                Далее мы похоже повторяемся, так что пожалуй закончим.


    1. creker
      08.11.2016 11:42

      Такая неявная реклама своей статьи, да.

      Сразу говорю, буду говорить применительно к C#, но это, считай, уже reference реализация async/await для всех остальных.

      При чем здесь однопоточная многозадачность? async-await вообще ортогональны этому. Поток может быть один, несколько, а сама реализация какой угодно.

      Если говорить об исключениях, то в том же C# стектрейс не выглядит конечно идентично синхронному коду, но среди вспомогательных методов содержит имена функций и конкретные строки кода всех вызовов, где выброшено исключение. Проблем тут никаких. Отладка так вообще прозрачная.

      Пенальти по производительности — async/await можно использовать по-разному (в том числе для выноса в фоновые потоки задач), но чаще всего используют этот механизм там, где на оверхед пофиг. То же сетевое взаимодействие существенно упрощается, а иначе все равно пришлось бы в callback-hell возвращаться, что не сильно быстрее в любом случае.

      Переписывание кода — когда библиотека уже вся на async-await, то ничего страшного не происходит. Нужно просто принять это как должное и писать по-новому, а не огораживать async-await код от всего остального.


      1. vintage
        08.11.2016 12:43
        +1

        Такая неявная реклама своей статьи, да.

        Вы так говорите будто это я кручу рекламу по всему Хабру. А если бы статья была не моя, то что-то сильно бы изменилось?


        При чем здесь однопоточная многозадачность? async-await вообще ортогональны этому. Поток может быть один, несколько, а сама реализация какой угодно.

        При том, что безопасно и эффективно они выполняются только в одном потоке. Собственно, это их основное назначение — уменьшить число нативных потоков, выполняя по несколько задач одновременно в каждом.


        Если говорить об исключениях, то в том же C# стектрейс не выглядит конечно идентично синхронному коду, но среди вспомогательных методов содержит имена функций и конкретные строки кода всех вызовов, где выброшено исключение. Проблем тут никаких. Отладка так вообще прозрачная.

        Это касается лишь stackfull coroutines, которые обычно реализуются без этих ваших async-await. В MS извратнулись и по началу сделали stackfull coroutines через async-await, а недавно переделали на stackless со всеми вытекающими.


        То же сетевое взаимодействие существенно упрощается, а иначе все равно пришлось бы в callback-hell возвращаться, что не сильно быстрее в любом случае.

        Вы всё же почитайте ту "рекламируемую статью". Там в конце приводится пример использования stackfull coroutines без async/await/yield/callbacks. Помимо V8, она используется в таких языках как Go, D, Python.


        Переписывание кода — когда библиотека уже вся на async-await, то ничего страшного не происходит.

        Происходит как минимум следующее:


        1. Приходится захламлять код всеми этими async/await.
        2. Приходится лепить костыли и дублировать код, для поддержки и того и того варианта использования в обобщённом коде (чтобы можно было передать асинхронный колбэк, нужно функцию делать асинхронной, но тогда её нельзя вызвать из синхронного кода, передав синхронный колбэк — нужно дублировать реализацию или ещё как костылять).


        1. creker
          08.11.2016 13:29
          -1

          При том, что безопасно и эффективно они выполняются только в одном потоке. Собственно, это их основное назначение — уменьшить число нативных потоков, выполняя по несколько задач одновременно в каждом.

          Это ваши личные выдумки. Задача async/await — упростить асинхронный код. К асинхронному коду относятся и один поток, и несколько. Вот вам обыденный пример — сокеты. Они не однопоточны, все вызовы уходят на completion порты и прочие механизмы, а это значит использование пула потоков ОС. async/await здесь нужен для скрытия всех этих деталей и возвращения управления в тот же поток, откуда произошел вызов, создавая иллюзию синхронного кода. async/await ортогональны каким-то там потокам.

          Это касается лишь stackfull coroutines, которые обычно реализуются без этих ваших async-await. В MS извратнулись и по началу сделали stackfull coroutines через async-await, а недавно переделали на stackless со всеми вытекающими.

          Замечательно, только стектрейс исключения в C# все так же содержит все вызовы моего кода + вспомогательные вызовы, которые вставил компилятор для реализации async/await, но их несложно фильтровать глазами. Это полезный стектрейс. Скомпилируйте hello world в студии и сами все увидите.

          Происходит как минимум следующее:

          Не происходит. Хватит теоретизировать и рекламировать свою статью, лучше код напишите сами. async/await захламляют разве что в легаси коде, но тут еще вопрос, что тут является хламом, async/await или легаси код. Как только код нормально можно перенести на этот механизм, то можно выкидывать десятки и сотни строк говнокода, который был раньше.
          Насчет костылей вообще непонятно откуда вы это все выдумали.


          1. vintage
            08.11.2016 14:49
            +1

            Задача async/await — упростить асинхронный код.

            Задача async — транслировать обычную функцию в машину состояний с соответствующими пенальти по производительности.
            Задача await — остановить текущую задачу, увеличив счётчик, и передать управление другой задаче в том же потоке. Иначе можно было бы выполнить thread_join безо всяких await и получить то же самое "упрощение асинхронного кода", но без необходимости транслировать все функции в машины состояний.


            Это полезный стектрейс. Скомпилируйте hello world в студии и сами все увидите.

            Я не C# разработчик. Можете сделать пример и указать в какой студии компилировали?


            Насчет костылей вообще непонятно откуда вы это все выдумали.

            Из практики я всё это выдумал. Говнокод был только у тех, кто не знал про fibers. Теперь они прикрутили костыли в виде async functions и хвастаются как теперь стало классно, сравнивая не с fibers, а со своим же говнокодом на колбэках.


  1. andreich
    08.11.2016 10:27

    -del


  1. vintage
    08.11.2016 15:24
    +1

    Или вот ещё пример. Внимание, реклама моей статьи!
    В главе "Исключительные ситуации", рассматривается пример синхронного кода, который под капотом делает параллельные неблокирующие запросы:


    @ $mol_mem()
    greeting() {
    
        const config = $mol_http_resource_json.item( './config.json' ).json()
        // тут запустился первый асинхронный запрос.
    
        const profile = $mol_http_resource_json.item( './profile.json' ).json()
        // тут запустился второй асинхронный запрос.
    
        const greeting = config.greeting.replace( '{name}' , profile.name )
        // а вот тут данные ответа потребовались и произошла остановка задачи
    
        // сюда управление уже дойдёт лишь, когда все асинхронные запросы отработают.
        return greeting
    }

    К сожалению fibers в браузере нет, так что то же самое реализуется несколько по другому, но суть та же — синхронизация происходит не в момент, когда мы делаем запрос, а в момент, когда без ответа не можем продолжать исполнение. Это позволяет эффективно распараллеливать задачи, не шаманя вручную над агрегированием ответов в один await, что в общем случае всё-равно не даст распараллелить всё.


    1. fogone
      08.11.2016 15:59

      Не совсем понимаю, чтобы такой код написать и корутин никаких не нужно:

      // посылает запрос асинхронно, для простоты возвращает просто String
      fun httpResourceJson(url: String): CompletableFuture<String> {
      // ...
      }
      
      fun greeting(): String {
      
          val config = httpResourceJson("./config.json")
          // тут запустился первый асинхронный запрос.
      
          val profile = httpResourceJson("./profile.json")
          // тут запустился второй асинхронный запрос.
      
          val greeting = config.get().replace("{name}", profile.get())
          // а вот тут данные ответа потребовались и произошла остановка задачи
      
          // сюда управление уже дойдёт лишь, когда все асинхронные запросы отработают.
          return greeting
      }
      


      1. fogone
        08.11.2016 16:03

        С локальными делегатами, которые тоже скоро появятся в языке, код будет вообще почти идентичным, только не нужна никакая магия:

        fun greeting(): String {
        
            val config by httpResourceJson("./config.json")
            // тут запустился первый асинхронный запрос.
        
            val profile by httpResourceJson("./profile.json")
            // тут запустился второй асинхронный запрос.
        
            val greeting = config.replace("{name}", profile)
            // а вот тут данные ответа потребовались и произошла остановка задачи
        
            // сюда управление уже дойдёт лишь, когда все асинхронные запросы отработают.
            return greeting
        }
        


      1. fogone
        08.11.2016 16:20

        Конечно, если мы хотим освободить поток до получения обоих значений, то нужно использовать что-то вроде await/async:

        fun greeting() = async {
        
            val configFuture = httpResourceJson("./config.json")
            // тут запустился первый асинхронный запрос.
        
            val profileFuture = httpResourceJson("./profile.json")
            // тут запустился второй асинхронный запрос.
            
            val (config, profile) = awaitPair(configFuture, profileFuture)
            // нам нужны результаты обоих заначений, ждем, пока поток отдали под другие нужды
            
            // сюда управление уже дойдёт лишь, когда все асинхронные запросы отработают.
            val greeting = config.replace("{name}", profile)
        
            return greeting
        }
        


      1. vintage
        08.11.2016 21:46
        +1

        Фишка вся в том, что $mol_http_resource_json может сходить за данными на сервер, а может сразу вернуть их из кеша. И использующему их коду не нужно знать возможен ли там где-то в глубине асинхронный запрос и соответственно беспокоиться о том, чтобы вызвать get() непосредственно перед использованием, но не раньше.


        Очень уж заманчиво написать так:


        fun greeting(): String {
        
            val config = httpResourceJson("./config.json").get()
            val profile = httpResourceJson("./profile.json").get()
        
            val greeting = config.greeting.replace( config.namePlaceHolder , profile[ config.nameField ] )
        
            return greeting
        }

        Вместо правильного:


        fun greeting(): String {
        
            val config = httpResourceJson("./config.json")
            val profile = httpResourceJson("./profile.json")
        
            val greeting = config.get().greeting.replace( config.get().namePlaceHolder , profile.get()[ config.get().nameField ] )
        
            return greeting
        }

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


        1. fogone
          09.11.2016 13:00

          Фишка вся в том, что $mol_http_resource_json может сходить за данными на сервер, а может сразу вернуть их из кеша.

          Это вообще никак не связано с темой нашего обсуждения — если данные уже получены, то их можно вернуть. Это логика функции httpResourceJson.
          Вместо правильного:

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

          В общем случае, если нам нужен результат, то это точка для await-а — т.е. если для каждого get-а мы будем саспендиться и освобождать поток (если готового значения еще нет), то это будет вполне рабочее поведение по-умолчанию.


  1. xGromMx
    08.11.2016 16:47

    Есть еще https://github.com/Kotlin/anko/blob/master/doc/ADVANCED.md#asynchronous-tasks


    1. andreich
      08.11.2016 17:11

      да, но это немного другое.


  1. pilgr
    08.11.2016 20:56

    Еще в попилку https://github.com/metalabdesign/AsyncAwait
    Библиотека, которую мы начали разрабатывать немного раньше описанной в статье. Предоставляет много больше возможностей как `awaitWithProgress`, избавляет от утечек памяти при уничтожении активити, обработку ошибок и пр.