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

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

Avatar for Yuki Anzai Yuki Anzai
October 19, 2018

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

Avatar for Yuki Anzai

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. (株式会社ウフィカ)