Как заставить сопрограммы запускаться последовательно из внешнего вызова

#kotlin #kotlin-coroutines

#kotlin #kotlin-сопрограммы

Вопрос:

Я действительно новичок в сопрограммах и в том, как это работает, я много читал об этом, но, похоже, не могу понять, как и могу ли я достичь своей конечной цели.

Я постараюсь объяснить как можно подробнее. В любом случае, вот моя цель:

Ensure that coroutines run sequentially when a method that has said coroutine is called.

Я создал тест, который соответствует тому, что я хотел бы получить:

 class TestCoroutines {

  @Test
  fun test() {
    println("Starting...")

    runSequentially("A")
    runSequentially("B")

    Thread.sleep(1000)
  }

  fun runSequentially(testCase: String) {
    GlobalScope.launch {
      println("Running test $testCase")
      println("Test $testCase ended")
    }
  }
}
  

Важное примечание: я не могу контролировать, сколько раз кто-то будет вызывать runSequentially функцию. Но я хочу гарантировать, что они будут вызываться по порядку.

В этом тесте выполняются следующие результаты:

 Starting...
Running test B
Running test A
Test A ended
Test B ended

Starting...
Running test A
Running test B
Test B ended
Test A ended

This is the output I want to achieve :
Starting...     
Running test A
Test A ended
Running test B
Test B ended
  

Я думаю, я понимаю, почему это происходит: каждый раз, когда я звоню runSequentially , я создаю новое задание, в котором оно выполняется, и которое выполняется асинхронно.

Возможно ли с помощью сопрограмм гарантировать, что они будут запускаться только после завершения предыдущей (если она запущена), когда у нас нет контроля над тем, сколько раз вызывается указанная сопрограмма?

Комментарии:

1. Существует очень простой способ добиться желаемого результата: использовать runBlocking вместо launch . В противном случае на самом деле не существует понятия «предыдущая сопрограмма», поэтому «чтобы гарантировать, что они будут выполняться только после завершения предыдущей (если она запущена)», неясно. Можете ли вы указать, что разрешено изменять в тесте?

2. Блокировка запуска действительно достигает желаемого результата, но она блокирует основной поток, чего в моем случае использования произойти не может.

Ответ №1:

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

 private val testCaseChannel = GlobalScope.actor<String>(
        capacity = Channel.UNLIMITED
) {
    for (testCase in channel) {
        println("Running test $testCase")
        println("Test $testCase ended")
    }
}

fun runSequentially(testCase: String) = testCaseChannel.sendBlocking(testCase)