Skip to content

Add doBlocking() utility method for running coroutines. #4440

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

kevinhwang
Copy link

This is a useful utility method used in places where you want to run a coroutine for its side effect.

E.g.,

fun main() = doBlocking {
  // ...
}
@Test
fun myTest() = doBlocking {
  // ...
}

With runBlocking(), if the last line of the coroutine block being run doesn't evaluate to Unit, e.g.:

fun main() {
  runBlocking {
    // ...
    Unit
  }
}

the compiler will complain about being unable to infer the type variable T of the block. Inserting a "return value" of Unit at the end is necessary to tell the compiler the block's return type.

Similarly, if you do

@Test
fun myTest() = runBlocking {
  // ...
   Unit
}

without the explicit Unit "return value" at the end, if the last line evaluates to a non-unit type, you'll get a type mismatch error, or the compiler will infer myTest()'s return type to be non-Unit.

@dkhalanskyjb
Copy link
Collaborator

The expected way to use runBlocking in main is

fun main() {
    runBlocking {
        // your code
    }
}

the compiler will complain about being unable to infer the type variable T of the block.

Could you provide a specific example?

@kevinhwang
Copy link
Author

The expected way to use runBlocking in main is

Within Google it's common to define single-expression functions using runBlocking and doBlocking to run a coroutine from a non-coroutine function (e.g., main, or a unit test method) and block on it:

fun main() = doBlocking {
  // ...
}

It's a nice short-hand and convenient, leading to one less level of nested braces / scopes to be read. But runBlocking causes problems if the block passed to it evaluates to anything other than Unit—then type inference will cause the function (e.g., main, or the test method) to have a non-Unit return type.

Could you provide a specific example?

Yeah, see https://stackoverflow.com/questions/58875520.

@dkhalanskyjb
Copy link
Collaborator

Yeah, see https://stackoverflow.com/questions/58875520.

Could not reproduce. The code compiles just fine for me, the tests don't run because of JUnit's logic.

But runBlocking causes problems if the block passed to it evaluates to anything other than Unit

Yes. When using runBlocking as a statement and not a (value-returning) expression, the single-expression form should be avoided.

I understand the point about the single-expression form being more convenient (in fact, our own codebase does use this form non-idiomatically to save an indentation level even when we return Unit), and typically, we do try to introduce shorthand forms for common idioms to streamline the process of writing code.

runBlocking is special, however. Because of how widely misused runBlocking is (#4242), making using it (or functions similar to it) more streamlined goes against our goals. In the linked issue, we are actually discussing how to make it less streamlined.

I can imagine us adding a withContext form that doesn't return a value (like launch can be thought of as a non-value-returning async), for example, to handle this case:

suspend fun main() = withContext(Dispatchers.IO) {
    // something
}

I haven't seen this problem in real code yet, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants