darth jemico
darth jemico

Reputation: 737

Non-blocking coroutine example

I'm just starting learning coroutines and managed to write a simple blocking one, that's aimed on a retry handling of a remote rest service. It returns me an eception if he can't currently handle foo update. But we have an agreement that i'll try to update foo next 10 seconds with an interval of 1 second:

private fun updateFoo(fooId: String): Foo {
    val updatedFoo = runBlocking {
        return@runBlocking withTimeoutOrNull(10L) {
            var result: Foo?
            do {
                result = try {
                    fooClient.update(fooId)
                } catch (ex: Exception) {
                    null
                }
                if (result != null) {
                    return@withTimeoutOrNull result
                }
                delay(1L)
            } while (result == null)
            return@withTimeoutOrNull result
        }
    }
    return updatedFoo ?: throw IllegalStateException(
        "Could not update Foo ($fooId) with retries.")
}

The point is that i run coroutine blocked. And ten seconds is a big enough interval, so i want to refactor this method to run coroutine nonBlcking.

I've tried to use launch { ... } instead of runBlocking, but i get Job as a result, but that's not what i want, i guess. Could anyone help me to write it correctly???

Upvotes: 0

Views: 1165

Answers (1)

Raman
Raman

Reputation: 19565

Assuming fooClient.update is already a suspending function, then your problem really seems to be about how to bridge the non-coroutines world that updateFoo lives in (as it is a non-suspending function) with the coroutines world of fooClient.update.

If that interpretation of the question is correct, then you'll need to use a traditional mechanism to obtain an async result from updateFoo, such as a deferred result or a callback.

If your updateFoo code ran in an async block you could return the Deferred<Foo?> value e.g.:

  fun updateFoo(fooId: String): Deferred<Foo?> {
    return GlobalScope.async {
      return@async withTimeoutOrNull(10L) {
        var result: Foo?
        do {
          result = try {
            update(fooId)
          } catch (ex: Exception) {
            null
          }
          if (result != null) {
            return@withTimeoutOrNull result
          }
          delay(1L)
        } while (result == null)
        return@withTimeoutOrNull result
      }
    }
  }

and then you could convert that to a CompletableFuture in your non-coroutines code e.g.

asCompletableFuture()

NOTE: I've used GlobalScope here for simplicity buts its generally a good idea to use an explicit scope, depending on your application's requirements.

Your commented that fooClient.update is actually a non-suspending blocking function. Given that, you should run it in an appropriate pool of threads that is configured specifically for blocking calls like this, such as Dispatchers.IO:

withContext(Dispatchers.IO) {
  update(fooId)
}

When run like this, the coroutine will not block -- the withContext call suspends.

Upvotes: 1

Related Questions