- Асинхронность в Java
- Синхронность vs асинхронность
- Синхронность:
- Асинхронность:
- Асинхронность с потоками
- Асинхронность с Future
- Асинхронность с CompletableFuture
- Асинхронность с @Async
- События Spring
- Микросервисы
- Плюсы и минусы асинхронности
- И последнее
- Беглый взгляд на Async-Await в Android
- Сценарий
- Старый добрый Thread
- AsyncTask андроида
- Callbacks
- Async-Await
- Achieving Async/Await in the Android Wasteland
- Async Workflows with Kotlin Pt I
- What Your Code Might Look Like: Part #1 — Loaders and AsyncTasks
- What Your Code Might Look Like: Part #2 — Callbacks
- What Your Code Might Look Like Part #3 — Kotlin Syntactic Sugar
- What Your Code Could Look Like: Kotlin Coroutines
- Async/Await with Kotlin Coroutines
- Optional Wrappers for Async/Await
Асинхронность в Java
Jul 23 · 5 min read
Асинхронное программирование — обширная и получившая широкое обсуждение тема, но инженеры-программисты все еще ищут, как лучше реализовать эту идею и интегрировать в приложения.
Мне — старшему инженеру-программисту — стало любопытно, как возможно делать несколько вещей одновременно, и задаюсь этим вопросом наверняка не только я. Каждый стремится быть более продуктивным и хочет того же от своих приложений.
Переключив внимание на асинхронность в Java, мы откроем для себя множество способов ее реализации и различные варианты использования.
Синхронность vs асинхронность
Синхронное (Sync) и асинхр о нное (Async) программирование может выполняться как в одном, так и в нескольких потоках. Основное различие между в том, что при синхронном программирования мы выполняем одну задачу за раз, а при асинхронном программировании — несколько задач выполняются одновременно. Например:
Синхронность:
- Однопоточность: я начинаю варить яйцо. После того как оно сварится, я могу начать поджаривать хлеб. Мне приходится ждать завершения одной задачи, чтобы начать другую.
- Многопоточность: я начинаю варить яйцо, а после того как оно сварится, моя мама поджарит хлеб. Задачи выполняются одна за другой и разными лицами (потоками).
Асинхронность:
- Однопоточность: я ставлю яйцо вариться и устанавливаю таймер, кладу хлеб в тостер и запускаю другой таймер, а когда время выйдет — я буду есть. В асинхронном режиме мне не нужно ждать завершения задачи, чтобы начать еще одну.
- Многопоточность: я нанимаю двух поваров, чтобы они сварили для меня яйцо и поджарили хлеб. Они могут делать это одновременно, и один не должен ждать другого, чтобы начать.
Асинхронность с потоками
Первый способ реализовать асинхронность в Java — воспользоваться интерфейсом Runnable и классом потока Thread , который доступен начиная с JDK 1.0. Любой класс может реализовать Runnable и переопределить метод run() либо расширить класс Thread и сделать то же самое.
Разница в том, что когда метод run вызывается непосредственно из Runnable , не создается новый поток, а метод выполняется в потоке, откуда вызван. Однако, если мы воспользуемся thread.start() , будет создан новый поток.
Для лучшего управления потоками в JDK 1.5 можно задействовать исполнителей ( Executor ). Они используют разные пулы потоков и помогают избежать необходимости вручную создавать поток. Вместо этого можно указать, сколько потоков нам нужно, и исполнитель будет переиспользовать эти потоки в течение всего времени запуска приложения.
Асинхронность с Future
run() — это void-метод, и он не может ничего возвращать из потока, но если нам нужен результат вычисления, выполняемого в другом потоке, чем main , то нужно будет воспользоваться интерфейсом Callable . Ответ от задачи недоступен немедленно, и в качестве альтернативы Callable вернет будущий объект Future , когда он будет отправлен в службу выполнения. Этот объект обещает, что, когда вычисления завершатся, мы получим их результат — достаточно только вызвать get() . Это не очень хорошее применение асинхронности, так как get() блокирует текущий поток до тех пор, пока не получит ответ. Однако существует обходной путь через метод future.isDone() — он постоянно проверяет, завершено ли вычисление, и только когда этот метод вернет значение true , get() возвратит результат.
Асинхронность с CompletableFuture
В JDK 1.8 объект Future получил обновление и стал объектом CompletableFuture , который, помимо будущего объекта, также реализует этап завершения ( CompletionStage ). CompletionStage предлагает множество методов для упрощения работы с ответами, вычисленными в разных потоках и этапах. Некоторые из наиболее распространенных — это thenApply() , аналогичная функции map() из потоков, а также thenAccept() , аналогичная функции foreach . Существует несколько способов получить ответ CompletableFuture . Одни выполняют задачу в другом потоке, другие нет, но их объединяет одно — если во время вычисления возникнут исключения, пользователи могут обрабатывать их.
Асинхронность с @Async
Другой способ реализации асинхронности — аннотация @Async из Spring Framework. Ее можно задействовать только в публичных методах, и в этом случае вызов методов из того же класса, в котором они определены, будет недоступен. Любой код, находящийся внутри метода с аннотацией @Async , будет выполняться в другом потоке и может быть недействительным или возвращать CompletableFuture . Таким образом, это альтернатива созданию CompletableFuture и предоставлению ему метода для запуска, но чтобы иметь возможность использовать эту аннотацию, необходимо другое: @EnableAsync в классе конфигурации.
События Spring
События Spring для реализации асинхронности — это шаг вперед, который также предлагает способ снижения связности и простоту добавления новых функций без изменения существующих.
Необходимы три элемента:
- Событие ( Event ) — может быть любым объектом, расширяющим ApplicationEvent .
- Издатель ( Publisher ) — компонент, который опубликует событие с помощью компонента ApplicationEventPublisher .
- Прослушиватель ( Listener ) — компонент, который содержит метод с аннотацией @EventListener , и помогает определить задачу, которая выполнится при возникновении определенного события.
По умолчанию метод прослушивателя выполняется синхронно, но это легко изменить, применив аннотацию @Async .
Другой способ сделать прослушиватель асинхронным — добавить в конфигурацию компонент с SimpleApplicationEventMulticaster и назначить ему TaskExecutor . Когда этот компонент на месте, не нужно аннотировать каждый список событий с помощью @Async , и все события будут сами обрабатываться в другом потоке. Если не хочется пропустить аннотацию @Async для какого-то метода, будет полезно воспользоваться этим способом, но имейте в виду: такой подход сделает асинхронной обработку всех событий, даже событий фреймворка. Использование аннотации позволит нам выбрать, какие события будут обрабатываться синхронно, а какие асинхронно.
Микросервисы
На уровне микросервисов также есть возможность выбирать между синхронностью и асинхронностью. Разница между ними в том, что, как и сказано в определении, асинхронность означает, что мы не ждем немедленного ответа от вызываемой службы, в то время как синхронность означает, что ждем.
Одна из самых популярных синхронных коммуникаций между микросервисами — через вызовы REST. Для асинхронной связи можно воспользоваться очередями или темами. И те, и другие содержат сообщения, но отличие в том, что сообщение из очереди может быть обработано только одним подписчиком, а сообщение из темы может быть прочитано несколькими подписчиками.
Плюсы и минусы асинхронности
Об асинхронном программировании стоит задуматься, когда вы хотите делегировать задачу другому потоку, поскольку она отнимает много времени, или не хотите, чтобы результат задачи влиял на текущий поток приложения. Так получится выполнять несколько операций одновременно. Используя асинхронность, вы можете разделять задачи и компоненты, что приводит к повышению общей производительности приложения.
В качестве альтернативы необходимо знать, что в коде с асинхронными методами усложняются отладка и написание тестов, но это не должно становиться препятствием при выборе решения.
И последнее
Потоки — это о работниках (воркерах), а асинхронность — это о задачах!
Источник
Беглый взгляд на Async-Await в Android
От переводчика
Это мой первый перевод, поэтому прошу прощения за неточности. Если вы найдете ошибки в переводе, пожалуйста, сообщите об этом. Я не нашел лучшего перевода слова сoroutine, чем сопрограмма, поэтому решил использовать оригинал. Если у вас появятся идеи по этому поводу, буду рад узнать.
Kotlin версии 1.1 принесет в язык coroutin’ы, которые позволяют приостанавливать вычисления в какой-то точке, а затем продолжить их позднее. Очевидный пример этой возможности — async-await, который был добавлен в C# несколько лет назад.
Каждый android разработчик знает, что когда мы имеем дело с сетевыми запросами или другими I/O задачами, то нам необходимо удостовериться, что не происходит блокировка основного потока, а так же, что мы не трогаем UI из фонового потока. На протяжении многих лет приходят и уходят десятки приемов. В этой статье перечислены наиболее популярные, и показаны примеры удобства, которое несет с собой async-await.
Сценарий
Мы хотим получить данные пользователя Github и положить их в базу данных, а после показать результат на экране. Я не стал объяснять подходы, они скажут всё сами за себя.
Старый добрый Thread
Ручное управление, полный контроль
AsyncTask андроида
Никто же их не использует больше, верно?
Callbacks
А Callback-hell кто-ниубдь использует?
Предоставляет крутые вещи.
Async-Await
А как вы смотрите на это?
Тут asyncUI (и аналогичный async ) метод включет функционал coroutin’ы, который предоставляет доступ к методу await. Каждый раз, когда выполнение достигает метода await, вычисления приостанавливаются до тех пор, пока параметр не будет вычислен, но поток, в котором произошел вызов, не блокируется. После этого coroutine продолжит свое выполнение. Метод asyncUI гарантирует, что выполнение продолжится в главном потоке.
Как вы заметили, coroutine повышает читаемость кода. Они доступны уже сейчас в версии kotlin 1.1-M02. Последний пример async-await использует библиотеку, которую я написал для возможности использования coroutines на Android. Если хотите больше узнать о coroutin’ах, то можете ознакомиться с неформальным описанием
PS: Эта статья не содержит отмены выполнений и удаления слушателей, которые могут содержать ссылки на активити. Каждый подход может иметь схожий вариант, но без утечек.
В следующей статье я сделал более детальный разбор async-await.
Источник
Achieving Async/Await in the Android Wasteland
Async Workflows with Kotlin Pt I
Nov 14, 2017 · 7 min read
This post is part of a series in which I set out how to implement common async-handling patterns in Android and Java using Kotlin Coroutines. If you haven’t seen them, or just aren’t as up to date as you’d like, have a squiz:
I recently spent six weeks on a new project, building an E xpressJS app in TypeScript with es2017 transpilation and support for async/await . Then I came back to my Android project that was still — like most Android projects — mired in callback hell for all of its async code. Of course, Java 8 adds CompleteableFuture , but this is only helpful if you’re only targeting API level 24 and up, and is unhelpful in general if you’re stuck in Java 6 or 7 altogether.
So I set out to try and achieve async task execution, satisfying some minimum criteria:
- No boilerplate, no overriding of class methods, no callback signatures
- Easily compose-able syntax, with simple parallel- and sequential- chaining
- Transparent thread use; no managing background and UI thread execution
- Be Java 6 or 7 compatible, support Android API 14+, optionally use Kotlin
The goal was to write code that would look something (roughly) like this:
I’d already adopted Kotlin in my Android project, and that turned out to be a necessary component. If you haven’t adopted Kotlin yet for a Java project, you should.
What Your Code Might Look Like: Part #1 — Loaders and AsyncTasks
These were obtuse, too tightly bound to activity lifecycle to be particularly flexible, and their pros just don’t outweigh their cons.
Here’s an example AsyncTask
Concrete class implementation for every unit of asynchronous work breaks our developer flow and cohesion, and is inherently not compose-able.
Loaders behave the same, though with the added pro of tightly coupling with an Activity lifecycle. This is often good, but not very reusable.
What Your Code Might Look Like: Part #2 — Callbacks
Most http libraries for Android, like Volley or OkHttp, yield a callback interface to the developer. Failing this, many devs — myself included — baked their own callback system on top of the base Android classes.
I think everybody on Earth knows what a callback looks like, but here’s an example from OkHttp:
It’s better. Async services run atomic pieces of work, and can be executed anywhere. Compose-ability is up, boilerplate is down.
What Your Code Might Look Like Part #3 — Kotlin Syntactic Sugar
This is less historic and more ‘now’.
Kotlin lets us do callbacks as above, but prettier. For example, here’s an callback object using a builder-like syntax.
Then start invoking asynchronous functions that return Callback objects, like so:
Boilerplate way down, but they’re still callbacks. Compose-ability and chaining is still a bust.
What Your Code Could Look Like: Kotlin Coroutines
Kotlin 1.1 introduced coroutines on the JVM as an experimental feature, with optional bindings for Android. If you’re not too familiar with what a coroutine is, I don’t blame you. They’re a bit magical.
A coroutine is, basically, like a micro-thread. They define a block of work that can be carried out on a coroutine runner, and a coroutine runner is something smart enough to know how to schedule itself on and off of a pool of worker threads, in small enough slices that the threads aren’t blocked for long enough to cause a problem.
Coroutines work in conjunction with something called a suspending function, which is basically just a function that can be paused and resumed by the runner.
Here’s how you’d run a coroutine on Android
Note that the really special thing here is that you can call doWork() from the UI thread of your app, and not only with that delay(500) not block your UI thread at all, but the updates to hello.text (which is a TextView ) won’t complain about being run off of the UI thread. Magic!
So let’s make it happen for doing async work, starting by adding it to a project.
Adding coroutines is straightforward enough. If you’ve already added Kotlin to your project, then just add coroutines as a gradle dependency by adding to your build.gradle
and then in your gradle.properties file, enable them with
Now you’re ready to go.
Async/Await with Kotlin Coroutines
Taking the OkHttp code from earlier, it first needs to be adapted so that it’s able to be execute synchronously — because coroutines run synchronously. OkHttp handily provides a synchronous recipe, and other libraries like Volley can be made to take a RequestFuture which can then be waited on.
Here’s OkHttp running synchronously:
Normally, running this on the UI thread would of course be a blocking call that would stall all UI interactions. So just wrap it into a coroutine.
Easy. Now it’ll run on the UI thread, but should be non-blocking. However, even though it’s non-blocking, it still pollutes the UI thread, potentially adding some lag or stutter, because the work is still happening within the UI thread pool. That’s not quite ideal. Instead, the updateView. calls needs to run on the UI, and the request needs to run on a worker pool.
Notice now how we start a coroutine bound to the UI context, but within it we start another async coroutine, which will be bound to the CommonPool . async returns a Deferred which can be awaited, so the http work gets done in another pool, and the UI coroutine suspends until it’s complete.
Optional Wrappers for Async/Await
You can definitely take the above and run with it. It’s as easy as that. A lot of other discussion and solutions on Kotlin Coroutines take what I feel is a heavy-handed approach with their own bindings. This is a matter of personal taste, and certainly doesn’t diminish such approaches — they’re really excellent! — I just prefer minimalism with my own definitions, and prefer to avoid dependencies where I can.
So here’s three lines of code I’ve found really helpful.
The first is a wrapper to just neaten up the entrypoint — in Android, all coroutine work is liable to be launched from the UI thread, so cleaning up that launch(UI) < … >syntax into something simpler, like coroutine < . >is a win.
The next two wrappers are to introduce an async/await syntax more similar to what you’d see in JavaScript/es2017.
This just defines extension methods on CoroutineScope , which is the scope provided within launch (or coroutine ). Now within those scopes, you can await a function block that yields a Deferred , or awaitAsync on any synchronous block, so working with Deferred ’s can be skipped. Note the use of suspend which means this function is able to be suspended by a coroutine runner, and hence is permitted to call await .
Now we write code that looks more like this
Which is semantically more alike to what you would see in es2017, where you would await an async function at the call-site.
Источник