[Перевод] Беглый взгляд на Async-Await в Android
Это мой первый перевод, поэтому прошу прощения за неточности. Если вы найдете ошибки в переводе, пожалуйста, сообщите об этом. Я не нашел лучшего перевода слова с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() {
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 {
override fun onResponse(call: Call, response: Response) {
val user = response.body()
userRepository.storeCallback(user) {
callbacksTV.text = "callbacks: [$user]"
}
}
override fun onFailure(call: Call, 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: Эта статья не содержит отмены выполнений и удаления слушателей, которые могут содержать ссылки на активити. Каждый подход может иметь схожий вариант, но без утечек.
В ближайшее время появится перевод продолжения.
Комментарии (3)
8 ноября 2016 в 09:36
–4↑
↓
Тут разбираются различные модели однопотоxой многозадачности на примере NodeJS, но те же выводы применимы для любой платформы. async functions, generators, promises, callbacks — теряют стек вызова, из-за чего их сложнее отлаживать, имеют пенальти по производительности и требуют переписывания всего кода приложения «в своём стиле».
8 ноября 2016 в 10:28 (комментарий был изменён)
0↑
↓
Про остальное не скажу, но вот на счет колбеков и async-await, то в котлине стектрейс сохраняется. Да и вообще, без колбеков в андроид разработке сложновато будет жить. Что касается производительности, то за многие вкусности приходится платить. Обычно тут вопрос стоит ли оно того или нет.
8 ноября 2016 в 10:27 (комментарий был изменён)
0↑
↓
-del