CoroutineがネットワークI/O待ちになったときに他のCoroutineが実行されるのか

# Coroutine(コルーチン)はネットワークI/O待ちになると他のCoroutineが実行されるのか？

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">Coroutineが通信処理のようなI/O待ちになったとき、そのCoroutineを実行していたスレッドはブロッキングされずに、他のCoroutineが実行されるんだろうか。されない気がするけどこれ実験するの面倒くさい奴だな、どうしよう。</p>&mdash; 竹内裕昭 (@takke) <a href="https://twitter.com/takke/status/1098074195911532545?ref_src=twsrc%5Etfw">February 20, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">単にタイムアウトというかめっちゃSleepしてから返却するようなURLを用意して大量のCoroutine作って叩いてみればいいんだな。AsyncTaskだとすぐに渋滞するのは何度も経験した。</p>&mdash; 竹内裕昭 (@takke) <a href="https://twitter.com/takke/status/1098074851598729217?ref_src=twsrc%5Etfw">February 20, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


・・・という疑問を解消するために少し実験してみました。


# AsyncTask の場合

まずはさくっと AsyncTask で実験します。

事前に適当な PHP スクリプトを置いておきます。

```php:sleep.php
<?php
sleep(2);
echo date(DATE_ATOM);
?>
```

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val tag = javaClass.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        client = OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .build()

        Handler().post {
            startNetworkEventByAsyncTask()
        }
    }

    private fun startNetworkEventByAsyncTask() {

        repeat(20) {
            val taskName = "async-task#$it"
            MyTask(taskName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
        }
    }

    class MyTask(private val taskName: String) : AsyncTask<Void, Void, Void>() {

        private val tag = javaClass.simpleName

        override fun doInBackground(vararg params: Void?): Void? {

            Log.d(tag, "$taskName: start")

            val response = doNetworkTask()
            response.use {
                Log.d(tag, "$taskName: response=" + response.code())
            }

            return null
        }

        override fun onPostExecute(result: Void?) {
            Log.d(tag, "$taskName: done")
        }
    }

    companion object {

        lateinit var client: OkHttpClient

        private fun doNetworkTask(): Response {
            val request = Request.Builder()
                .url("https://自分のサーバ/sleep.php")
                .build()

            return client.newCall(request).execute()
        }
    }
}
```

実行結果がこちら↓

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">AsyncTaskでOkHttp3で通信する場合：4スレッドずつ処理されていく <a href="https://t.co/vgRWj2nyrl">pic.twitter.com/vgRWj2nyrl</a></p>&mdash; 竹内裕昭 (@takke) <a href="https://twitter.com/takke/status/1098095570671923200?ref_src=twsrc%5Etfw">2019年2月20日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

見事に4スレッドずつ渋滞しながら処理されていきます。

# Coroutineの場合

```kotlin:MainActivity.kt
    private fun startNetworkEventByCoroutine() {

        repeat(20) {
            val taskName = "coroutine#$it"
            GlobalScope.launch(Dispatchers.Main) {

                withContext(Dispatchers.IO) {

                    Log.d(tag, "$taskName: start")

                    val response = doNetworkTask()
                    response.use {
                        Log.d(tag, "$taskName: response=" + response.code())
                    }
                }

                Log.d(tag, "$taskName: done")
            }
        }
    }
```

実行結果がこちら↓

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">CoroutineでOkHttp3で通信する場合：20個全部がほぼ同時にリクエストされている <a href="https://t.co/r0J79nxfWE">pic.twitter.com/r0J79nxfWE</a></p>&mdash; 竹内裕昭 (@takke) <a href="https://twitter.com/takke/status/1098096186714537984?ref_src=twsrc%5Etfw">2019年2月20日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ちょっと分かりにくいですが、ちゃんと20個全部が start に到達し、その後順に response を受けていきます。最初に同時に受け取ったレスポンスが9個しかないのはサーバ側の同時実行数の問題ですね。。

```
2019-02-20 14:43:09.246 8097-8153/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#0: start
2019-02-20 14:43:09.251 8097-8154/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#1: start
2019-02-20 14:43:09.252 8097-8156/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#2: start
2019-02-20 14:43:09.258 8097-8160/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#5: start
2019-02-20 14:43:09.268 8097-8155/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#6: start
2019-02-20 14:43:09.268 8097-8161/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#7: start
2019-02-20 14:43:09.270 8097-8157/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#3: start
2019-02-20 14:43:09.272 8097-8158/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#4: start
2019-02-20 14:43:09.276 8097-8164/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#9: start
2019-02-20 14:43:09.276 8097-8163/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#8: start
2019-02-20 14:43:09.277 8097-8162/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#10: start
2019-02-20 14:43:09.285 8097-8159/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#11: start
2019-02-20 14:43:09.292 8097-8168/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#13: start
2019-02-20 14:43:09.292 8097-8172/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#12: start
2019-02-20 14:43:09.297 8097-8165/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#15: start
2019-02-20 14:43:09.298 8097-8180/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#16: start
2019-02-20 14:43:09.300 8097-8189/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#19: start
2019-02-20 14:43:09.302 8097-8185/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#18: start
2019-02-20 14:43:09.305 8097-8179/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#17: start
2019-02-20 14:43:09.307 8097-8170/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#14: start
2019-02-20 14:43:11.692 8097-8180/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#16: response=200
2019-02-20 14:43:11.694 8097-8172/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#12: response=200
2019-02-20 14:43:11.694 8097-8168/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#13: response=200
2019-02-20 14:43:11.694 8097-8153/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#0: response=200
2019-02-20 14:43:11.695 8097-8161/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#7: response=200
2019-02-20 14:43:11.695 8097-8162/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#10: response=200
2019-02-20 14:43:11.695 8097-8165/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#15: response=200
2019-02-20 14:43:11.696 8097-8156/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#2: response=200
2019-02-20 14:43:11.696 8097-8154/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#1: response=200
2019-02-20 14:43:11.696 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#7: done
2019-02-20 14:43:11.696 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#10: done
2019-02-20 14:43:11.696 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#16: done
2019-02-20 14:43:11.697 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#15: done
2019-02-20 14:43:11.697 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#2: done
2019-02-20 14:43:11.697 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#0: done
2019-02-20 14:43:11.699 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#12: done
2019-02-20 14:43:11.700 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#13: done
2019-02-20 14:43:11.702 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#1: done
2019-02-20 14:43:12.211 8097-8170/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#14: response=200
2019-02-20 14:43:12.219 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#14: done
2019-02-20 14:43:13.228 8097-8157/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#3: response=200
2019-02-20 14:43:13.235 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#3: done
2019-02-20 14:43:13.240 8097-8160/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#5: response=200
2019-02-20 14:43:13.244 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#5: done
2019-02-20 14:43:14.234 8097-8158/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#4: response=200
2019-02-20 14:43:14.234 8097-8185/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#18: response=200
2019-02-20 14:43:14.246 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#4: done
2019-02-20 14:43:14.247 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#18: done
2019-02-20 14:43:14.248 8097-8155/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#6: response=200
2019-02-20 14:43:14.249 8097-8189/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#19: response=200
2019-02-20 14:43:14.252 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#6: done
2019-02-20 14:43:14.255 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#19: done
2019-02-20 14:43:14.255 8097-8179/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#17: response=200
2019-02-20 14:43:14.256 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#17: done
2019-02-20 14:43:14.414 8097-8164/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#9: response=200
2019-02-20 14:43:14.421 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#9: done
2019-02-20 14:43:15.149 8097-8159/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#11: response=200
2019-02-20 14:43:15.150 8097-8163/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#8: response=200
2019-02-20 14:43:15.154 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#11: done
2019-02-20 14:43:15.155 8097-8097/com.example.coroutinenetworkblockingsample D/MainActivity: coroutine#8: done
```


# まとめ


CoroutineでネットワークI/O待ちになってもちゃんと他のCoroutineが実行される。



AsyncTaskのようなスレッド渋滞は起きない。



当たり前の結果なんだろうけど、Coroutineラクですねぇ。


検証コード一式は https://github.com/takke/CoroutineNetworkBlockingSample に置きました。


# 追記：単にスレッドがたくさん作られてるだけでした。

例えば100個実行してみると、64個で「渋滞」しました。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">でもなんかCoroutineが実際にいくつのスレッドで実行されてるのか調べてないしちょっと足りない気がしてきた</p>&mdash; 竹内裕昭 (@takke) <a href="https://twitter.com/takke/status/1098103932776177664?ref_src=twsrc%5Etfw">February 20, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a href="https://t.co/P3PYcRtZb2">https://t.co/P3PYcRtZb2</a> だと、スレッド生成された気はしますー。<a href="https://t.co/yOzPORKKqz">https://t.co/yOzPORKKqz</a><br><br>&gt; uses a shared pool of on-demand created threads and is designed for offloading of IO-intensive blocking operations (like file I/O and blocking socket I/O).</p>&mdash; Tomoya Miwa (@tomoya0x00) <a href="https://twitter.com/tomoya0x00/status/1098104609992626176?ref_src=twsrc%5Etfw">February 20, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">おお、そうなんですね！確かにめっちゃスレッド作られてるだけですねｗｗ</p>&mdash; 竹内裕昭 (@takke) <a href="https://twitter.com/takke/status/1098105896364691456?ref_src=twsrc%5Etfw">February 20, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">はいww<br><br>Dispatchers.Default だと、スレッド数が固定（確か、コア数 - 1）なので、AsyncTaskと同じように渋滞するとお見ます。</p>&mdash; Tomoya Miwa (@tomoya0x00) <a href="https://twitter.com/tomoya0x00/status/1098106513682358272?ref_src=twsrc%5Etfw">February 20, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">確かに Dispatchers.Default だと手元の PH-1 では 8個ずつ渋滞していました。</p>&mdash; 竹内裕昭 (@takke) <a href="https://twitter.com/takke/status/1098107280489234437?ref_src=twsrc%5Etfw">February 20, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">ということで、<a href="https://t.co/yiGoclEViU">https://t.co/yiGoclEViU</a> を使うと 64 個とか大量のスレッドが作られちゃって逆にヤバイので気をつけて使わねば。Dispatchers.Default だとスレッド数上限が限られてるので AsyncTask と同じような動きをする、と。</p>&mdash; 竹内裕昭 (@takke) <a href="https://twitter.com/takke/status/1098107858242043904?ref_src=twsrc%5Etfw">February 20, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>