261

I am currently trying to leverage kotlin coroutines more. But I face a problem: when using moshi or okhttp inside these coroutines I get a warning:

"inappropriate blocking method call"

What is the best way to fix these? I really do not want to be inappropriate ;-)

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
ligi
  • 39,001
  • 44
  • 144
  • 244

11 Answers11

111

The warning is about methods that block current thread and coroutine cannot be properly suspended. This way, you lose all benefits of coroutines and downgrade to one job per thread again.

Each case should be handled in a different way. For suspendable http calls you can use ktor http client. But sometimes there is no library for your case, so you can either write your own solution or ignore this warning.

Edit: withContext(Dispatchers.IO) or some custom dispatcher can be used to workaround the problem. Thanks for the comments.

theapache64
  • 10,926
  • 9
  • 65
  • 108
Evgeny Bovykin
  • 2,572
  • 2
  • 14
  • 27
  • 74
    Ignoring the warning is almost never the right thing, you can at least run the blocking code in `withContext(Dispatchers.IO)`. – Marko Topolnik Nov 27 '19 at 09:51
  • 35
    if you run the blocking code with `withContext(Dispatchers.IO)` then it isn't blocking anymore and the warning isn't correct, right? – noloman Jan 10 '20 at 08:13
  • 9
    @noloman good question because "at least" assumes knowledge that somebody who is asking this question does not have. Still the warning does not go away when you introduce this wrapper. – Alex Apr 13 '20 at 18:08
  • 5
    @noloman A blocking call is still a blocking call but `Dispatchers.IO` can tolerate blocking calls because unlike `Dispatchers.Default` it can create as many threads as it needs. Other dispatchers could run out of threads if they all get locked up by blocking calls. – Tenfour04 Jul 09 '20 at 15:51
  • 33
    @Tenfour04 so if we wrap the code in `withContext(Dispatchers.IO)` then the warning is incorrect? AndroidStudio still displays it for me, unless I drop the suspend and use `runBlocking(Dispatchers.IO)` instead. – Stachu Oct 23 '20 at 15:45
  • 4
    @Stachu There are some false positives bug reports like this (https://youtrack.jetbrains.com/issue/KT-39684) so that's possible. But there are blocking calls that intentionally trigger the warning even for Dispatchers.IO, such as `Thread.sleep()`, which is a code smell in any place in a coroutine. – Tenfour04 Oct 23 '20 at 16:04
  • 2
    @Tenfour04, [Dispatchers.IO](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html) also has a limit of 64 threads or the number of cores (whichever is larger). – Cube Mar 29 '21 at 13:21
  • 58
    adding ```withContext(Dispatchers....)``` did not remove the warning – jzqa Apr 07 '21 at 09:40
  • 1
    Dispatchers.IO not works in my case , IOUtils.toByteArray & contentResolver.openInputStream – Hossein Kurd Aug 10 '21 at 13:28
  • 9
    So what is the correct answer? Just wrap the blocking code with `withContext(Dispatchers.IO)` and ignore the warning? – Arst Sep 25 '21 at 03:57
  • 1
    try wrap the code here kotlin.runCatching { // your code here } – Aaeb Jan 08 '22 at 19:58
  • This was acknowledged as an overeager inspection. Dispatchers.IO is the right way to do this, but it's hard to prove work back up the context path and determine whether it's running in safe context. https://youtrack.jetbrains.com/issue/KTIJ-838 – user1575326 Feb 04 '22 at 15:56
  • 2
    It is fixed as of IntelliJ 2022.1. It is even a suggestion from the editor to use `withContext(Dispatchers.IO)` – Nicofisi May 12 '22 at 18:45
  • 1
    I'm running `Android Studio 2021.1.1 Patch 3 Build #AI-211.7628.21.2111.8309675, on March 16, 2022`, and now a statement like `FileInputStream(File("dummy.txt"))` is flagged even inside `withContext(Dispatchers.IO)` – Marko Topolnik May 14 '22 at 14:31
  • 1
    Just adding withContext(Dispatchers.IO) will not help if you are calling Java blocking methods like BufferedReader.readLine(). In these cases you will still get the warning. – Monte Creasor May 25 '22 at 20:21
  • Any final Answer Here??? – Mohammad Sommakia Jun 18 '22 at 07:57
  • 1
    @user1575326 Can you please specify in which documentation they recommend it? Why people simply post their opinions whithout any reference and call it "This is how it must be done"?! – Farid Jul 22 '22 at 16:24
  • @MonteCreasor can you explain a bit more about why calling a java blocking method or any other blocking method which is not suspendable on Dispatchers.IO is a problem? – Sarthak_ssg5 Oct 01 '22 at 10:22
  • @Sarthak_ssg5 I think there are 2 issues. One is that where possible, you should use suspendable functions so that the coroutine framework will retain control of the calling thread to run other coroutines as it sees fit. The other reason is that, as someone else pointed out) suspendable functions/methods play nicely with coroutine cancellation. – Monte Creasor Oct 03 '22 at 02:17
87

Exceptions can occur that's why it shows this warning. Use runCatching{}. It catches any Throwable exception that was thrown from the block function execution and encapsulating it as a failure.

For Example:

 CoroutineScope(Dispatchers.IO).launch {
         runCatching{
               makeHttpRequest(URL(downloadLocation))
         }
}
Syed Umair
  • 1,480
  • 1
  • 13
  • 14
  • 28
    This works but still looks like a hack or an exploit to circumvent Kotlin Plugin's buggy detection of inappropriate blocking calls. Because essentially what this method does is just wrap the problematic call in a `try ... catch` block which somehow makes it invisible to the inspection. – Inego Mar 19 '21 at 10:14
  • 4
    It worked for me in both FileInputStream and FileOutputStream modes. Thanks – AllanRibas Jul 05 '21 at 06:04
  • 1
    The weird thing is that `try ... catch` is not the same as `runCatching`. Only `runCatching` will hide the error, even though the 2 are the same. – andras Mar 10 '22 at 19:37
  • runCatching does silence the warning, but doesn't tackle the issue highlighted by the warning. It's better to add suppression annotation to the method, than using runCatching ( @Suppress("BlockingMethodInNonBlockingContext") ) – Malachiasz Aug 24 '22 at 08:59
71

You also get this warning when calling a suspending function that is annotated with @Throws(IOException::class) (Kotlin 1.3.61). Not sure if that is intended or not. Anyway, you can suppress this warning by removing that annotation or changing it to Exception class.

Mark
  • 7,446
  • 5
  • 55
  • 75
  • 5
    Let's call it suppressing, not fixing :) I suppose, static analyzer highlights it because the method which throws IOException is usually blocking and takes some valuable time to finish. – Ivan Shafran May 04 '20 at 00:07
  • 9
    I found this explanation about IOException helpful: https://discuss.kotlinlang.org/t/warning-inappropriate-blocking-method-call-with-coroutines-how-to-fix/16903/2 – Ivan Shafran May 04 '20 at 00:13
  • 1
    For me, I found it's `InterruptedException::class` will cause the warning. – KFJK Oct 27 '21 at 01:30
31

Wrap the "inappropriate blocking method call" code in another context using withContext.

That is to say (for example):

If you are doing a read/write blocking method call:

val objects = withContext(Dispatchers.IO) { dao.getAll() }

If you are performing a blocking network request (using Retrofit):

val response = withContext(Dispatchers.IO) { call.execute() }

Or if you are performing a CPU intensive blocking task:

val sortedUsers = withContext(Dispatchers.Default) { users.sortByName() }

This will suspend the current coroutine, then execute the "inappropriate blocking call" on a different thread (from either the Dispatchers.IO or Dispatchers.Default pools), thereby not blocking the thread your coroutine is executing on.

Zulkifil
  • 337
  • 4
  • 4
  • 15
    I don't get it. I tried using Dispatchers.IO and it still shows a warning. I added `Thread.sleep` to simulate long waiting there. I want to be able to let it be cancelled via any means that I could using threads (interruptions and checking if it's cancelled). – android developer Jan 30 '21 at 20:47
  • 1
    This problem is related to Kotlin coroutines (asynchronous programming). If you use `Thread.sleep`, you are directly manipulating the underlying (Java architecture) thread. Use the `suspendCoroutine` function inside a coroutine scope to make the coroutine wait for a job to finish, while the underlying thread is free to continue executing other coroutines. – Zulkifil Feb 12 '21 at 11:35
  • 1
    Since Thread.sleep is a valid case of functions (can be done in functions that I call, for example, which I have no control of what they have inside), I can't replace it. – android developer Feb 13 '21 at 23:57
  • Can you share the code that gives the warning? – Zulkifil Feb 14 '21 at 01:05
  • 9
    Adding the above withContext does not work either – Jono Mar 01 '21 at 14:41
  • What specific function call in what library results in the generation of the warning? – Zulkifil Mar 05 '21 at 07:58
19

If you do choose to suppress like some of the answers suggest, use

@Suppress("BlockingMethodInNonBlockingContext")

seekingStillness
  • 4,833
  • 5
  • 38
  • 68
  • I use a helper class that runs code in `Dispatchers.IO`. Despite of this it shows the warning in `val response = someMethod().execute` and `response.body()?.string()`. So, I just suppress the warning. – CoolMind Oct 06 '20 at 08:48
  • Why would someone in their right mind suppress such a warning? This is more than a warning. This means you just crippled the whole coroutine arcitecture and better not use it. – Farid Jan 05 '22 at 20:15
  • @Farid One example: calling readLine() in a coroutine when reading from local csv file is ok, but will provide this warning and it can be locally suppressed – seekingStillness Jan 06 '22 at 00:10
  • No, it is not okay. What if you have multiple coroutines reading files from local? If any coroutine hits `runBlocking` then the whole thread will be blocked. Meaning other coroutines will wait until `runBlocking` finishes which is against the purpose of coroutines – Farid Jan 06 '22 at 06:07
  • 1
    @Farid. nope doesn't happen, it's been tested – seekingStillness Jan 06 '22 at 11:47
  • @seekingStillness What do you mean by no? It is even in official documentation (https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html)which reads "This function should not be used from coroutine". Do you mind sharing your "tests" that actually disprove the documentation? – Farid Jul 25 '22 at 06:21
12

I'm using Android Studio 4.1, and the warning shows when I use Moshi or manipulate File. Wrapping the code in a withContext doesn't help even if I'm sure about what I'm doing.

I recently found out that moving the tiny code that warns into a standard method without suspend like fun action() {...} can remove the warning. This is ugly since it simply hides the warning.

Update: From my personal experience, it appears suppressing the warning or runBlocking is more straightforward.

Update2: After upgrading to Android Studio Flamingo(AGP 8), some warnings disappear; for those who don't, wrapping code in a withContext(Dispatchers.IO) sometimes remove the warning. A related explanation: https://youtu.be/zluKcazgkV4?t=2400

Dewey Reed
  • 4,353
  • 2
  • 27
  • 41
  • Don't use `runBLocking` in your production code at all. Someone will use it in coroutines and everything will be clocked and become a mess. https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html – Farid Jul 25 '22 at 06:22
2

It looks like encasing the call in kotlin.runCatching() resolves the warning, not sure why though... Because as previous answer about runCatching states it's not due to the exception throwing since even try{} catch doesn't resolve the issue, could be some buggy detection issue... I ended up using the below for now...

val result = kotlin.runCatching {
     OldJavaLib.blockingCallThatThrowsAnException()
}
if (result.isSuccess) {
      print("success is on your side")
} else {
      print("one failure is never the end")
}
David Aleksanyan
  • 2,953
  • 4
  • 29
  • 39
  • 1
    runCatching does silence the warning, but doesn't tackle the issue highlighted by the warning. It's better to add suppression annotation to the method, than using runCatching ( @Suppress("BlockingMethodInNonBlockingContext") ) – Malachiasz Aug 24 '22 at 08:58
2

A solution is to wrap blocking code via suspend fun kotlinx.coroutines.runInterruptible. Without it, the blocking code won't be interrupted, when coroutine's job gets cancelled.

It suppressed compile warning and blocking code will throw InterruptedException on cancellation

val job = launch {
  runInterruptible(Dispatchers.IO) {
    Thread.sleep(500) // example blocking code
  }
}

job.cancelAndJoin() // Cause will be 'java.lang.InterruptedException'

https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-interruptible.html

How to convert Java blocking function into cancellable suspend function?

Malachiasz
  • 7,126
  • 2
  • 35
  • 49
0

This is how you suspend your Coroutine, run your blocking method in a thread, and resume it on result. This will also handle exceptions, so your app won't crash.

suspendCoroutine { continuation ->
    thread {
        try {
            doHttpRequest(URL(...)) {
                continuation.resume(it)
            }
        }
        catch (t: Throwable) {
            continuation.resumeWithException(t)
        }
    }
}

Edit: The intended way to do it is to use witchContext(Dispatchers.IO). I leave this response here in case someone finds this approach useful.

sulai
  • 5,204
  • 2
  • 29
  • 44
-2

I ran into the same issue today, here goes the solution worked for me. Hope it helps!

        CoroutineScope(Dispatchers.IO).launch {

        val call = client.newCall(request)
        call.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                print("Internet access, 4G, Wifi, DNS, etc failed")
            }
    
            override fun onResponse(call: Call, response: Response) {
                if(response.isSuccessful) {
                    print("Server accepted!")
                } else {
                    print("Server failed!")
                }
            }
        })
    }

Keep in mind this callbacks are consumed only once. You can't use it into other threads.

Marcelo
  • 65
  • 2
  • 10
-7

I used dispatchers as launch arguments:

    GlobalScope.launch(Dispatchers.IO) {
        // Do background work
        
        // Back to main thread
        launch(Dispatchers.Main) {
            Toast.makeText(context, "SUCCESS!", Toast.LENGTH_LONG)
                .show()
        }
    }
fullmoon
  • 8,030
  • 5
  • 43
  • 58
  • 9
    Very bad idea. You're passing context into an uncontrolled coroutine. You should avoid using GlobalScope https://elizarov.medium.com/the-reason-to-avoid-globalscope-835337445abc – Edhar Khimich Jul 25 '21 at 18:20
  • It works and answers the question, there are always better ways to do things depending on the use case, in my case I have a relatively small one activity app. – fullmoon Mar 23 '22 at 23:11
  • 1
    You could have posted the "better" answer instead of "it works and answers question" type of answer. There are lots of new people to this concept and you are intentionally misleading them – Farid Jul 28 '22 at 05:37
  • @EdgarKhimich I read that blog and attempted in my code. Android Studio raise the warning point in the original question. I also understood the approach pointed in the blog is in the case you "stack" several threads at one point. I am not sure if other GlobalScope in other parts of the code would have this issue as well. – Marcelo Jul 30 '22 at 00:09