Slide 1

Slide 1 text

async/await Rubyでどう書く? Fukuoka.rb 0x100 LT @YusukeIwaki

Slide 2

Slide 2 text

About @YusukeIwaki ● 仕事:天神にある会社で、Railsアプリケーションの開発 ○ 生まれは富山、大学は京都、1社目は札幌、2018年に福岡に移住 ● puppeteer-rubyとかplaywright-ruby-clientとか作ってる人 ○ ブラウザを自動操作するやつ ○ Rubyでブラウザ操作するのに、Selenium以外の選択肢を作りたい。

Slide 3

Slide 3 text

Fukuoka.rb 0x0100回おめでとうございます ● Kaigi on Rails 2021登壇のきっかけは、Fukuoka.rbの夜会 ○ 夜の23時まで開いているRubyコミュニティは多分ここだけ? ○ 福岡にゆかりのあるひともないひともつながれるRubyコミュニティは素晴らしい ○ 今後もゆるゆるとRubyを盛り上げていきましょう

Slide 4

Slide 4 text

本日の話 async / await ってRubyだとどうやって使えばいいの?

Slide 5

Slide 5 text

JSの有名ライブラリ Railsから使いたい! →Rubyに移植したい! async Promise then catch Promise.all Promise.race await

Slide 6

Slide 6 text

おそらく以下のどっちかが現実解 ● 思考停止でconcurrent-rubyを使う ○ とくにRailsアプリケーションだったら手軽 ● めっちゃ考えてAsync gemを使う ○ シングルスレッド動作で最大限のパフォーマンスが出せる

Slide 7

Slide 7 text

(memo) ここまで1分

Slide 8

Slide 8 text

おさらい: RubyのThreadとFiber ● Thread→思考停止で、2つ以上の処理を同時に流せる ● Fiber→ゆずりあいで、2つ以上の処理をかわりばんこに流す ○ 合言葉は Fiber.yield

Slide 9

Slide 9 text

% ruby fiber.rb 0 1 2 3 4 0 1 2 3 4 % ruby thread.rb 0 0 1 1 2 2 3 3 4 4

Slide 10

Slide 10 text

async/await in Ruby ● 思考停止でconcurrent-rubyを使う ○ Threadベース ○ とくにRailsアプリケーションだったら手軽 ● めっちゃ考えてAsync gemを使う ○ Fiberベース ○ シングルスレッド動作で最大限のパフォーマンスが出せる

Slide 11

Slide 11 text

(memo) ここまで2分半

Slide 12

Slide 12 text

concurrent-rubyでの async/await Promise Concurrent::PromisesモジュールのFutureを使う。 https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs- source/promises.in.md

Slide 13

Slide 13 text

concurrent-ruby: 2種類のFuture Concurrent::Promises.future { ….. ; 123 } ● 裏で処理をやって、結果として123を返す ● Thread.newと同じ使い勝手で、さらに実行結果をもらえるイメージ f = Concurrent::Promises.resolvable_future, f.fulfill(123) , f.reject(err) ● スレッドセーフな箱を作って、結果123を別スレッドから入れてもらう ● JSのPromiseのような使い勝手

Slide 14

Slide 14 text

concurrent-ruby: Futureのチェーン、結果待ち受け future.then { |result| … ; next_result }.then { |next_result| … } ● JSのPromise#then, Promise#catch に限りなく近い future.value! ● futureの結果をもらう。 ● futureの結果がまだ出ていない場合には、結果が出るまでブロッキング。 Concurrent::Promises.zip(future1, future2, …) ● 全部の結果が出揃うのを待つ。JSのPromise.allと同じ。

Slide 15

Slide 15 text

async click(x, y) { const point = await clickablePoint(x, y) await scrollToPosition(x, y) await Promise.all([ mouse.down(x, y), mouse.up(x, y), ]) } def click(x, y) Concurrent::Promises.future do point = clickable_point(x, y).value! scroll_to_position(x, y).value! Concurrent::Promises.zip( mouse.down(x, y), mouse.up(x, y), ).value! end end concurrent-rubyを使ったasync/awaitっぽい実装 ※ 擬似コードです

Slide 16

Slide 16 text

でも気をつけろ!concurrent-rubyはThreadベースだ!

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

Async gem https://rubykaigi.org/2019/presentations/ioquatix.html

Slide 20

Slide 20 text

ノンブロッキングI/Oを活用し、IO待ちの間に別の処理を行う ● シングルスレッドのFiberベースなので、オーバーヘッドが非常に少ない Ruby 2.xで利用するときには ノンブロッキングI/Oベースの各種ライブラリを併用する必要がある Async gem

Slide 21

Slide 21 text

Async { … } / Async::Reactor.run { … } ● 処理のかたまり(task)を定義する ○ JSの async () => { … } と似ている ● トップレベルで利用するときと、ネストで利用するときとで挙動が違う ○ トップレベルで利用時・・・サブタスクが全部終わるまで待つ ○ ネストで利用時・・・サブタスクを作成し、スケジュール ○ これも JSの async function呼び出しの挙動と似ている Async gemの基本

Slide 22

Slide 22 text

q = Async::Queue.new, q.dequeue, q.enqueue(hoge) ● q.dequeueを呼ぶと、誰かがq.enqueueするまで待つ ● q.dequeueよりも先に誰かがenqueue(hoge)していたら、即座にhogeが返る ● ほぼPromise cond = Async::Condition.new, cond.wait, cond.signal(hoge) ● cond.waitを呼ぶと、誰かがcond.signalするまで待つ ● cond.waitよりも先に誰かがcond.signal(hoge)しても、signalは捨てられる ● 自前でPromiseっぽい何かを作る材料になる Async::Barrier, Async::Semaphore ● サブタスク全部が終わるまで待つ、サブタスクの同時実行数を制限する ● Promise.all 的なものを作る材料になる Async gemを使って async/await Promise

Slide 23

Slide 23 text

async click(x, y) { const point = await clickablePoint(x, y) await scrollToPosition(x, y) await Promise.all([ mouse.down(x, y), mouse.up(x, y), ]) } def click(x, y) Async do point = clickable_point(x, y).wait scroll_to_position(x, y).wait barrier = Async::Barrier.new barrier.async { mouse.down(x, y).wait } barrier.async { mouse.up(x, y).wait } barrier.wait end end Async gemを使ったasync/awaitっぽい実装 ※ 擬似コードです

Slide 24

Slide 24 text

でも気をつけろ!Async gemはFiberベースだ! ● 思考停止で使うと、並列処理されない(当然) ● 不用意にwaitすると、メインスレッドがブロックされ全部が動かなくなる ● Ruby 2.7で使いたいならノンブロッキングI/Oベースのライブラリ導入に そこそこ対応工数がかかる ただ、用途があえば ● すんごい速い ● すんごい安定 ○ 「ときどき処理順序がひっくり返ってしまって困る・・・」のような Threadベースだと起きる問題がFiberベースでは起きない

Slide 25

Slide 25 text

(memo) ここまで4分

Slide 26

Slide 26 text

まとめ:async/await in Rubyの現実解 ● 思考停止でconcurrent-rubyを使う ○ とくにRailsアプリケーションだったらもともと入ってるので手軽 ○ Threadベースなので、処理順序性の保証とかほぼない ● めっちゃ考えてAsync gemを使う ○ Fiberベースのシングルスレッド動作で最大限のパフォーマンスが出せる ○ JSのasync functionに近い動作 ○ Ruby 2.7環境で既存のアプリケーションに導入するのは結構たいへん

Slide 27

Slide 27 text

DEMO