Kotlin > @Async (非同期処理)

@Asyncを本番使用してはいけないという話もあり、要調査

Kotlin coroutines and spring framework


@Async


アプリケーションクラス


  • EnableAsyncをimport追加


  • @EnableAsyncアノテーションを追加


Application.kt

import org.springframework.scheduling.annotation.EnableAsync

@SpringBootApplication
@EnableAsync
class DemoApplication



コントローラー

関数名に@Asyncアノテーションをつける。

これだけで非同期に動作する。


HelloController.kt

package com.example.demo

import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Async
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import java.util.concurrent.TimeUnit

@RestController
class HelloController {

val logger = LoggerFactory.getLogger(this::class.java.name)

@GetMapping("/hello")
@Async
fun hello(): String {
logger.info("Standard Task Start")
TimeUnit.SECONDS.sleep(5)
logger.info("Standard Task End")
return "Hello! World!"
}
}


ターミナルで以下を実行

curl http://localhost:8080/hello

curl http://localhost:8080/hello
curl http://localhost:8080/hello

以下のような結果が返ってくる

2019-07-12 16:18:47.832  INFO 50509 --- [         task-1] com.example.demo.HelloController         : Standard Task Start

2019-07-12 16:18:47.858 INFO 50509 --- [ task-2] com.example.demo.HelloController : Standard Task Start
2019-07-12 16:18:47.878 INFO 50509 --- [ task-3] com.example.demo.HelloController : Standard Task Start
2019-07-12 16:18:52.837 INFO 50509 --- [ task-1] com.example.demo.HelloController : Standard Task End
2019-07-12 16:18:52.862 INFO 50509 --- [ task-2] com.example.demo.HelloController : Standard Task End
2019-07-12 16:18:52.881 INFO 50509 --- [ task-3] com.example.demo.HelloController : Standard Task End


非同期処理をコントローラーからサービス側へ移動する


HelloController.kt

package com.example.demo

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class HelloController {

@Autowired
private lateinit var service: HelloService

@GetMapping("/hello")
fun hello()= service.standardTask()

}


HelloService.ktを作成


HelloService.kt


package com.example.demo

import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit

@Service
class HelloService {

val logger = LoggerFactory.getLogger(this::class.java.name)

@Async
fun standardTask() {
logger.info("Standard Task Start")
TimeUnit.SECONDS.sleep(5)
logger.info("Standard Task End")
}
}



スレッド制御

@Asyncだけではコールされる毎にスレッドが作成される。

デフォルトでは、スレッド数の制限がない。

スレッド制御のために、TaskExecutorが使える。


Application.kt

@EnableAsync

class DemoApplication

@EnableAsync
class DemoApplication{

@Bean
fun normalTaskExecutor(): TaskExecutor = ThreadPoolTaskExecutor().apply {
corePoolSize = 1
setQueueCapacity(5)
maxPoolSize = 1
setThreadNamePrefix("NormalThread-")
setWaitForTasksToCompleteOnShutdown(true)
}
}


@Asyncに関数名を追加


HelloService.kt

    @Async

fun standardTask() {

@Async("normalTaskExecutor")
fun standardTask() {




  • corePoolSize: アイドルであってもプール内に維持されるスレッドの数

  • setQueueCapacity:


  • maximumPoolSize: プール内で可能なスレッドの最大数


  • keepAliveTime: スレッドの数がコアよりも多い場合、これは超過したアイドル状態のスレッドが新しいタスクを待機してから終了するまでの最大時間


  • unit: keepAliveTime 引数の時間単位


  • workQueue: タスクが超過するまで保持するために使用するキュー。このキューは、execute メソッドで送信された Runnable タスクだけを保持する


  • setWaitForTasksToCompleteOnShutdown:


  • setThreadNamePrefix:


キューがいっぱいにならないとcorePoolSize からmaximumPoolSizeまでスレッドが生成されない。


Spring 非同期タスクの同時実行数を制限する - Qiita

corePoolSizeまでThreadを作る

corePoolSizeが一杯になるとqueueCapacityまでキューイングする

queueCapacityを越えるとmaxPoolSizeまでThreadを増やす

maxPoolSizeを越えるとrejectされる



参考

special thanks

ことりんと一緒 Springもね - 6. 非同期処理 - Qiita

ThreadPoolTaskExecutor の挙動 - Qiita

Spring 非同期タスクの同時実行数を制限する - Qiita

BLOG.IK.AM - 誤解しがちなThreadPoolTaskExecutorの設定