Повторите задачу в течение времени с задержкой

#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. Просто для информации — вы не должны использовать инструменты потоков, когда вы достигаете параллелизма с сопрограммами. Это как бы сводит на нет весь смысл.