Upgrade to Pro — share decks privately, control downloads, hide ads and more …

KotlinConf 2018 ワークショップに参加してきた

Yuki Anzai
October 19, 2018

KotlinConf 2018 ワークショップに参加してきた

Yuki Anzai

October 19, 2018
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. Workshop day • 10⽉3⽇(KotlinConf day1) • カンファレンスとは別料⾦ : €649 •

    9:00 〜 17:00 • 朝ごはん・昼ごはんあり • 会場はカンファレンスと同じ • https://kotlinconf.com/workshops/
  2. Workshops • Up and running with Kotlin by Svetlana Isakova

    • Asynchronous Programming with Kotlin by Roman Elizarov • Developing Android Apps in Kotlin by Florina Muntenescu and Sean McQuillan • Building a Full Stack Web Application in Kotlin by Garth Gilmour • Refactoring to Kotlin by Duncan McGregor and Nat Pryce
  3. Asynchronous Programming with Kotlin • 講師 : Roman Elizarov •

    https://github.com/elizarov • 内容 : coroutines
  4. 事前準備 • 最新の IntelliJ IDEA (CE 可) をインストールしておく • Kotlin

    plugin の最新の EAP 1.3 version をインストールしておく • [Tools] → [Kotlin] → [Configure Kotlin Plugin Updates] → Early Access Preview 1.3 → Check & Install the latest version • Github ログイン⽤のトークン • [Settings] →[Developer Setting] → Personal Access Tokens → Generate New Token
  5. Outline • Asynchronous programming, Introduction to Coroutines • Coroutines vs

    Threads, Context and Cancellation • Coroutines and Concurrency • CSP with Channels and Actors
  6. サンプルプロジェクト • project/ContributorsUI.main() • デスクトップアプリ(Swing利⽤) • GitHub から kotlin Organization

    のリポジトリ⼀覧を取 得し、各リポジトリの contributors を取得し集計する • part1async/ 〜 part4csp/ • ⼩さい demo コード集
  7. • Kotlin Organization のリポジトリを取得 • List<Repo> • リポジトリごと Contributors を取得

    • List<User> • ユーザーの重複をflattenして集計 Repo1 - User11 - User12 - … Repo2 - User21 - User22 - … …
  8. enum class Variant { BLOCKING, // Request1Blocking BACKGROUND, // Request2Background

    CALLBACKS, // Request3Callbacks COROUTINE, // Request4Coroutine PROGRESS, // Request5Progress CANCELLABLE, // Request5Progress (too) CONCURRENT, // Request6Concurrent GATHER, // Request8Gather ACTOR // Request9Actor }
  9. Programming styles • 1. Blocking threads • 2. Callbacks •

    3. Futures • 4. Kotlin coroutines COROUTINE, // Request4Coroutine PROGRESS, // Request5Progress CANCELLABLE, // Request5Progress (t CONCURRENT, // Request6Concurrent GATHER, // Request8Gather ACTOR // Request9Actor FUTURE, // Request7Future CALLBACKS, // Request3Callbacks BACKGROUND, // Request2Background
  10. ウォーミングアップ data class User( val login: String, val contributions: Int

    ) fun List<User>.aggregate(): List<User> = TODO List<User> の中から重複した login の User を1つにまとめる まとめるとき contributions を⾜し合わせる contributions の多い順にソートする
  11. ウォーミングアップ fun List<User>.aggregate(): List<User> = groupingBy { it.login } .reduce

    { login, a, b -> User(login, a.contributions + b.contributions) } .values .sortedByDescending { it.contributions } data class User( val login: String, val contributions: Int ) List<User> の中から重複した login の User を1つにまとめる まとめるとき contributions を⾜し合わせる contributions の多い順にソートする
  12. Background fun loadContributorsBackground(req: RequestData, callback: (List<User>) -> Unit) { thread

    { val users = loadContributorsBlocking(req) callback(users) } } スレッドで
  13. Callbacks fun loadContributorsCallbacks(req: RequestData, callback: (List<User>) -> Unit) { val

    service = createGitHubService(req.username, req.password) service.listOrgRepos(req.org).responseCallback { repos -> … } } inline fun <T> Call<T>.responseCallback(crossinline callback: (T) -> Unit) { enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { checkResponse(response) callback(response.body()!!) } override fun onFailure(call: Call<T>, t: Throwable) { log.error("Call failed", t) } }) } Retrofit の enqueue を利⽤
  14. Coroutines launch { val users = loadContributors(req) updateResults(users) } suspend

    fun loadContributors(req: RequestData) : List<User> { val service = createGitHubService(req.username, req.password) val repos = service.listOrgRepos(req.org).await() val contribs = repos.flatMap { repo -> val users = service.listRepoContributors(req.org, repo.name).await() users }.aggregate() return contribs } loadContributors() 部分は blocking と同じ
  15. Progress suspend fun loadContributorsProgress(req: RequestData, callback: (List<User>) -> Unit) {

    val service = createGitHubService(req.username, req.password) val repos = service.listOrgRepos(req.org).await() var contribs = listOf<User>() for (repo in repos) { val users = service.listRepoContributors(req.org, repo.name).await() contribs = (contribs + users).aggregateSlow() callback(contribs) } } Coroutines + Progress リポジトリロードごとに callback 呼び出し
  16. Cancellable private fun Job.updateCancelJob() { updateEnabled(false) val listener = ActionListener

    { cancel() } cancel.addActionListener(listener) launch { join() updateEnabled(true) cancel.removeActionListener(listener) } } キャンセルボタンが押されたら Job を cancel() launch { loadContributorsProgress(req) { users -> updateResults(users) } }.updateCancelJob()
  17. Concurrent suspend fun loadContributorsConcurrent(req: RequestData): List<User> = coroutineScope { val

    service = createGitHubService(req.username, req.password) val repos = service.listOrgRepos(req.org).await() val contribs = repos.map { repo -> async { val users = service.listRepoContributors(req.org, repo.name).await() users } }.awaitAll().flatten().aggregate() contribs } async でリポジトリごとの Contributors ロードを並列化
  18. Future val future = loadContributorsConcurrentAsync(req) updateCancelFuture(future) future.thenAccept { users ->

    SwingUtilities.invokeLater { updateResults(users) } } fun loadContributorsConcurrentAsync( req: RequestData ): CompletableFuture<List<User>> = GlobalScope.future { loadContributorsConcurrent(req) }
  19. Gather suspend fun loadContributorsGather( req: RequestData, callback: suspend (List<User>) ->

    Unit ) = coroutineScope { val service = createGitHubService(req.username, req.password) val repos = service.listOrgRepos(req.org).await() val channel = Channel<List<User>>() for (repo in repos) { launch { val users = service.listRepoContributors(req.org, repo.name).await() channel.send(users) } } var contribs = emptyList<User>() repeat(repos.size) { val users = channel.receive() contribs = (contribs + users).aggregateSlow() callback(contribs) } } Channel を使⽤
  20. Actor suspend fun loadContributorsActor( req: RequestData, uiUpdateActor: SendChannel<List<User>> ) =

    coroutineScope<Unit> { val service = createGitHubService(req.username, req.password) val repos = service.listOrgRepos(req.org).await() val aggregator = aggregatorActor(uiUpdateActor) val requests = Channel<WorkerRequest>() val workers = List(4) { workerJob(requests, aggregator) } for (repo in repos) { requests.send(WorkerRequest(service, req.org, repo.name)) } requests.close() workers.joinAll() aggregator.close() } List<Repo> → requests : Channel<WorkerRequest> → aggregator: SendChannel<List<User>> → uiUpdateActor : SendChannel<List<User>> 4並列でWorkRequestを処理
  21. fun CoroutineScope.aggregatorActor( uiUpdateActor: SendChannel<List<User>> ) = actor<List<User>> { var contribs:

    List<User> = emptyList() // STATE for (users in channel) { contribs = (contribs + users).aggregateSlow() uiUpdateActor.send(contribs) } } class WorkerRequest( val service: GitHubService, val org: String, val repo: String ) fun CoroutineScope.workerJob( requests: ReceiveChannel<WorkerRequest>, aggregator: SendChannel<List<User>> ) = launch { for (req in requests) { val users = req.service.listRepoContributors(req.org, req.repo).await() log.info("${req.repo}: loaded ${users.size} contributors") aggregator.send(users) } } actor { } を使⽤
  22. Have a Nice Kotlin! • blog : Y.A.M の雑記帳 •

    y-anz-m.blogspot.com • twitter : @yanzm (やんざむ) • uPhyca Inc. (株式会社ウフィカ)