#android #kotlin #kotlin-coroutines
#Android #kotlin #kotlin-сопрограммы
Вопрос:
Мне нужно переместить индикатор выполнения в течение определенного периода времени, например, в течение 6 секунд. Я использую сопрограммы и функцию «повторить». Код выполняется, за исключением того, что общее время выполнения не указано. Ниже приведен мой код.
val progressJob = Job()
var startTime = 0L
CoroutineScope(Dispatchers.Default progressJob).launch {
startTime = System.currentTimeMillis()
repeat(1000) {
progressBar.progress = 1
delay(6)
}
Log.d(TAG, "total time= ${System.currentTimeMillis() - startTime}")
}
Я ожидаю, что «общее время» будет равно 6000, но я получаю значения, превышающие 6000, по крайней мере, на 500.
По сути, я просто хочу многократно увеличивать индикатор выполнения в течение определенного периода времени, и я не использую анимацию из-за проблем с производительностью.
Есть ли что-то, чего мне не хватает?
Ответ №1:
Я бы сделал это примерно так:
withTimeout(1300L) {
repeat(1000) { i ->
println("Blip Blop $i ...")
delay(500L)
}
}
Дополнительные примеры см. В Официальном документе:
https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html
Ответ №2:
итак, то, что вы здесь делаете, имитирует прогресс. В идеале, был бы какой-то способ проверить фактический прогресс вашего бара и обновить его, а когда это будет сделано, завершить. Но, если это невозможно, тогда ya, симуляция — ваш выбор.
Итак, с сопрограммами мы имеем дело с многопоточной средой, и внутри нее у нас есть наши сопрограммы, которые необходимо продолжить, когда передача контроля над выполнением. В вашей реализации это происходит при delay
вызове. По этой причине очень сложно гарантировать, что ваша сопрограмма завершится в желаемое время. Все, что может сделать delay, это сказать, что она не возобновится до истечения «по крайней мере» указанного времени, и, вероятно, довольно часто, прошло бы больше времени, а не точное время.
Итак, как нам выполнить это как можно ближе к желаемому сроку? Что нам нужно сделать, это отбросить repeat
и проверить прошедшее время, чтобы решить, закончим ли мы. Вот примерная реализация, которая, надеюсь, поможет.
class Bar(val barLength: Int = 1000) {
var progress = 0
}
suspend fun simulateProgress(bar: Bar, job: Job, totalDurationMillis: Long, incrementsMills: Long): Job {
var startTime = System.currentTimeMillis()
return CoroutineScope(Dispatchers.Default job).launch {
var totalElapsed = 0L
while (totalElapsed < totalDurationMillis) {
totalElapsed = System.currentTimeMillis() - startTime
val progressRatio = totalElapsed.toDouble()/totalDurationMillis.toDouble()
bar.progress = (progressRatio * bar.barLength.toDouble()).toInt()
delay(incrementsMills)
}
println("Elapsed: $totalElapsed, Progress: ${bar.progress}")
}
}
fun main() = runBlocking {
val job = Job()
val bar = Bar()
val progressJob = simulateProgress(bar, job, 6000, 10)
progressJob.join()
}
Комментарии:
1. Удаление
repeat
и замена наwhile
— это ключ. В среднем общее время составляет601X
, что достаточно близко для моего варианта использования. Большое спасибо.2. @HaytonLeung — нет проблем.
Ответ №3:
Сопрограмма не обеспечивает точное время. Если процессор одновременно занят выполнением других задач, сопрограммы могут быть легко отложены. Используйте класс Timer для точного определения времени. Вот пример. Я регистрирую время каждые секунды и отменяю таймер через 6 секунд. Результаты отклоняются всего на несколько миллисекунд.
var startTime = 0L
val timer : Timer = Timer()
val task = object : TimerTask()
{
var lastTime = 0L
override fun run() {
val now = System.currentTimeMillis()
if(now/1000 > lastTime/1000 )
{
Log.d("timer","total time= ${now - startTime}")
lastTime = now
}
if(now - startTime >= 6000)
{
timer.cancel()
}
}
startTime = System.currentTimeMillis()
timer.scheduleAtFixedRate(task,0,6)
Комментарии:
1. Для некоторых других требований мне придется придерживаться сопрограмм, но спасибо за ваше предложение.
Ответ №4:
Вы измеряете не только задержку в 6 миллисекунд, но и время, необходимое для выполнения for
цикла (скрытого в repeat
), а также время progressBar.progress = 1
и стоимость delay
самого цикла.
Например:
CoroutineScope(Dispatchers.Default progressJob).launch {
startTime = System.currentTimeMillis()
repeat(1000){
delay(6)
}
val endTime = System.currentTimeMillis() - startTime
println("total time= $endTime")
}
требуется 6751 мс (в среднем 100 запусков) на моей машине.
Если я использую Thread.sleep
вместо задержки:
CoroutineScope(Dispatchers.Default progressJob).launch {
startTime = System.currentTimeMillis()
repeat(1){
delay(6)
}
val endTime = System.currentTimeMillis() - startTime
println("total time= $endTime")
}
это занимает 6701 мс.
Если я выполняю повторение только один раз:
CoroutineScope(Dispatchers.Default progressJob).launch {
startTime = System.currentTimeMillis()
repeat(1){
Thread.sleep(6)
}
val endTime = System.currentTimeMillis() - startTime
println("total time= $endTime")
}
8 мс
Если я удалю повтор:
CoroutineScope(Dispatchers.Default progressJob).launch {
startTime = System.currentTimeMillis()
Thread.sleep(6)
val endTime = System.currentTimeMillis() - startTime
println("total time= $endTime")
}
Комментарии:
1. Спасибо за ваше объяснение. Я не заметил цикл for внутри
delay
, пока вы не укажете на него. Я думаю, это то, откуда берется дополнительное время2. Просто для информации — вы не должны использовать инструменты потоков, когда вы достигаете параллелизма с сопрограммами. Это как бы сводит на нет весь смысл.