LoginSignup
10
4

More than 5 years have passed since last update.

asyncでお手軽非同期プログラミング with Haskell

Last updated at Posted at 2019-04-04

こんにちは、pxfncです。今日はHackageが502を返してくる不機嫌な日でした(早く機嫌なおして)

asyncパッケージを使って非同期で遊んでみたので紹介しようと思います。

逐次処理の普通のプログラム

import           Control.Concurrent             ( threadDelay )

sleep :: Int -> IO ()
sleep n = threadDelay (1000000 * n)

main :: IO ()
main = do
    putStrLn "start"
    sleep 1
    putStrLn "1 second later" -- startから1秒後に表示
    sleep 2
    putStrLn "2 second later" -- startから3秒後に表示
    sleep 3
    putStrLn "3 second later" -- startから6秒後に表示
    putStrLn "done"           -- startから6秒後に表示

このプログラムは合計6秒かけて実行されます。これをasyncパッケージを使って非同期にしていきます。

async関数で非同期にする

n秒待ってから"n second later"と表示する部分をsleepAndPut関数にくくり出してみます。そして、sleepAndPut 1だけ非同期に実行してみましょう。やり方は簡単で、async 関数を、非同期にしたいIO aの値に適用するだけです。つまり、async (sleepAndPut 1)とするだけです

import           Control.Concurrent             ( threadDelay )
import           Control.Concurrent.Async       ( async )

sleep :: Int -> IO ()
sleep n = threadDelay (1000000 * n)

sleepAndPut :: Int -> IO ()
sleepAndPut n = do
    sleep n
    putStrLn $ show n <> " second later"

main :: IO ()
main = do
    putStrLn "start"
    async $ sleepAndPut 1 -- startから1秒後に表示
    sleepAndPut 2         -- startから2秒後に表示
    sleepAndPut 3         -- startから5秒後に表示
    putStrLn "done"       -- startから5秒後に表示

実行結果からわかることはasync $ sleepAndPut 1の呼び出しが瞬時に終了して次のアクションが実行されていることです。今度は1秒から3秒全て非同期にしてみましょう。先ほどのmain関数を以下のように書き換えて実行すると

main :: IO ()
main = do
    putStrLn "start"
    async $ sleepAndPut 1 -- startから1秒後に表示
    async $ sleepAndPut 2 -- startから2秒後に表示
    async $ sleepAndPut 3 -- startから3秒後に表示
    putStrLn "done"       -- start直後に表示!!

最初にstartが表示された後に即座にdoneが表示され、その後sleepのログが出てしまっています。このような表示になるのは、sleepAndPutの全ての呼び出しが即座に終了するからです。

waitを使って待機をする

実はasync :: IO a -> IO (Async a)の戻り値であるAsync aを使うことで非同期の終了を待機することが可能です。非同期の実行を待つ関数が、このwait :: Async a -> IO aです。

先ほどのプログラムを非同期処理が完了してから終了するように書き換えてみます

import           Control.Concurrent             ( threadDelay )
import           Control.Concurrent.Async       ( async, wait )

sleep :: Int -> IO ()
sleep n = threadDelay (1000000 * n)

sleepAndPut :: Int -> IO ()
sleepAndPut n = do
    sleep n
    putStrLn $ show n <> " second later"

main :: IO ()
main = do
    putStrLn "start"
    a1 <- async $ sleepAndPut 1 -- startから1秒後に表示
    a2 <- async $ sleepAndPut 2 -- startから2秒後に表示
    a3 <- async $ sleepAndPut 3 -- startから3秒後に表示
    wait a1
    wait a2
    wait a3
    putStrLn "done"             -- startから3秒後に表示

手っ取り早く非同期がしたいんだが?

mapMの代わりにmapConcurrentlyforMの代わりにforConcurrentlyreplicateMの代わりにreplicateConcurrentlyがあり、それぞれアクションを捨てる~~~Concurrently_関数がそれぞれ用意されています。先ほど処理はこのように書くことができます。

main :: IO ()
main = do
    putStrLn "start"
    forConcurrently_ [1 .. 3] sleepAndPut
    putStrLn "done"

とりあえずここまで

想像より簡単に非同期処理ができることがわかったかと思います。非同期処理のキャンセルや変数については別記事にて紹介する予定です。

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4