When using coroutines, why can't network work be done on the main thread? (Android)

138 views Asked by At

I understand that a coroutine does not block a thread and can delegate the thread to another coroutine while it is doing its work.

I'm wondering why I shouldn't do network work in a coroutine with Dispatcher.Main.

While doing network work, you will be able to perform other UI-related tasks without blocking the Main Thread.

What I was thinking is, even if you can give up the work to another coroutine, network work can take a long time, and as a result, is it because the main thread has a lot of UI-related work to do, which wastes resources?

// works well
lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                val service = RetrofitClient.buildService(GithubApiService::class.java)
                val response = service.getIssues("JakeWharton", "hugo")
                Log.d("TAG", " - response: $response")
            }
        }

@GET("/repos/{user}/{repo}/issues")
    suspend fun getIssues(
        @Path("user") user: String,
        @Path("repo") repo: String
    ): List<Issue>
5

There are 5 answers

12
Tenfour04 On

There is nothing wrong with using Dispatchers.Main to call your getIssues suspend function. It doesn't matter what dispatcher you are using to call suspend functions, because suspend functions do not block (unless you incorrectly defined the suspend function yourself by putting blocking code in it). You only have to avoid calling blocking functions on the main thread.

If you defined a blocking network function, it would freeze your app if you called it on the main thread.

1
buonlixi On

Main reason that network call can't be done on Main Thread, that it block the thread, and Main thread is the only one mustn't be blocked.

On the code above you provide, it can work because it's Retrofit's internal mechanism, network call will be dispatched to background thread if you mark retrofit function with suspend.

If you make pure network call with pure URL and HTTPConnection, It will be blocked. And cause app crash with NetworkOnMainThreadException

Here is example code of why you should not call network on Main Thread:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            TestAppTheme {
                val scope = rememberCoroutineScope()

                Column(Modifier.fillMaxSize(),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Center
                ) {
                    CircularProgressIndicator(
                        Modifier
                            .size(50.dp),
                        strokeWidth = 2.dp,
                        color = Color.Black
                    )
                    Spacer(Modifier.height(40.dp))
                    Button(onClick = {
                        scope.launch {
                            makeNetworkCall()
                        }
                    }) {
                        Text(text = "Call")
                    }
                }
            }
        }
    }
}

suspend fun makeNetworkCall() {
    val url = URL("https://www.google.com")
    val connection = (url.openConnection() as HttpURLConnection)
    connection.requestMethod = "GET"
    val wr = DataOutputStream(
        connection.outputStream
    )
    wr.close()

    val inputStream = connection.inputStream
    val rd = BufferedReader(InputStreamReader(inputStream))
    println(rd)
}

1
Gabe Sechan On

Why you shouldn't? There's a few reasons. The first is it would still likely crash. The strict mode policy enforcer doesn't know about coroutines. It's just going to check the thread that it's running on, and throw an exception if it's the main thread. It isn't smart enough to do anything else. And given that it needs to support Java which doesn't have coroutines, it would be difficult to make it smarter.

Another is how coroutines and the Android framework interact. Suspending allows another coroutine to run. But allowing another coroutine to run wouldn't allow other lifecycle functions to run (you need to finish your onCreate first). The looper that handles all the messages to send things to the main thread wouldn't be returned to (because onCreate didn't finish), so it wouldn't be able to process the next message. Now if you launch the coroutine all of that is avoided. But you couldn't run it in place.

A minor reason would be because you'd confuse the OS's scheduler. The scheduler tends to classify threads as IO or computational, trying to give longer time blocks to computation and shorter but more immediate ones to IO. Run your IO on the same thread as your main and the scheduler will get confused and run much less optimally. So even if you could run it on one thread it would be best not to.

1
MohamedMab On

simply because the main tread(UI thread) is responsible for displaying the ui of the screen so if you put too much work on it at the same time it can lead to your app being laggy and irresponsible .

0
rashmi raut On

Coroutines can be used in the main thread for network calls, but network calls may take more time because of multiple reasons, and for that time the main thread is blocked. Because the main thread is responsible for rendering the UI, it will give a bad user experience as well as it may result in ANR (Application Not Responding).