Haskell相当于C#5 async / await
我刚刚阅读了使用await
和async
关键字在C#5.0中处理异步函数的新方法。 从等待的C#参考考试:
private async Task SumPageSizesAsync() { // To use the HttpClient type in desktop apps, you must include a using directive and add a // reference for the System.Net.Http namespace. HttpClient client = new HttpClient(); // . . . Task getContentsTask = client.GetByteArrayAsync(url); byte[] urlContents = await getContentsTask; // Equivalently, now that you see how it works, you can write the same thing in a single line. //byte[] urlContents = await client.GetByteArrayAsync(url); // . . . }
Task
表示将生成byte[]
类型值的异步任务的Future。 在Task
上使用关键字await
将基本上将函数的其余部分放在一个延续中,这将在任务完成时调用。 任何使用await
函数都必须使用关键字async
并且如果它返回类型Task
则具有类型Task
。
这样的台词
byte[] urlContents = await getContentsTask; // Do something with urlContents
会翻译成类似的东西
Task newTask = getContentsTask.registerContinuation( byte[] urlContents => { // Do something with urlContents }); return newTask;
这感觉很像Monad(变形金刚?)。 感觉它应该与CPS monad有一些关系,但也许不是。
这是我尝试编写相应的Haskell类型
-- The monad that async functions should run in instance Monad Async -- The same as the the C# keyword await :: Async (Task a) -> Async a -- Returns the current Task, should wrap what corresponds to -- a async method in C#. asyncFunction :: Async a -> Async (Task a) -- Corresponds to the method Task.Run() taskRun :: a -> Task a
以及上述例子的粗略翻译
instance MonadIO Async -- Needed for this example sumPageSizesAsync :: Async (Task ()) sumPageSizesAsync = asyncFunction $ do client <- liftIO newHttpClient -- client :: HttpClient -- ... getContentsTask <- getByteArrayAsync client url -- getContentsTask :: Task [byte] urlContents <- await getContentsTask -- urlContents :: [byte] -- ...
这是Haskell中的相应类型吗? 是否有任何Haskell库(或类似方式)实现处理异步函数/操作的方法?
另外:你能用CPS变压器构建吗?
编辑
是的, Control.Concurrent.Async
模块确实解决了类似的问题(并且具有类似的接口),但是以完全不同的方式这样做。 我猜Control.Monad.Task
会更接近匹配。 什么(我认为)我正在寻找的是Futures的monadic界面,它在幕后使用Continuation Passing Style 。
这是一个构建在async
库之上的Task
monad:
import Control.Concurrent.Async (async, wait) newtype Task a = Task { fork :: IO (IO a) } newTask :: IO a -> Task a newTask io = Task $ do w <- async io return (wait w) instance Monad Task where return a = Task $ return (return a) m >>= f = newTask $ do aFut <- fork m a <- aFut bFut <- fork (fa) bFut
请注意,我没有检查monad法律,所以它可能不正确。
这是您定义在后台运行的原始任务的方法:
import Control.Concurrent (threadDelay) test1 :: Task Int test1 = newTask $ do threadDelay 1000000 -- Wait 1 second putStrLn "Hello," return 1 test2 :: Task Int test2 = newTask $ do threadDelay 1000000 putStrLn " world!" return 2
然后,您可以使用do
notation组合Task
,这会创建一个准备好运行的新延迟任务:
test3 :: Task Int test3 = do n1 <- test1 n2 <- test2 return (n1 + n2)
运行fork test3
将生成Task
并返回一个可以随时调用的未来以请求结果,必要时阻塞直到完成。
为了certificate它有效,我将做两个简单的测试。 首先,我将fork test3
而不要求它的未来只是为了确保它正确生成复合线程:
main = do fork test3 getLine -- wait without demanding the future
这工作正常:
$ ./task Hello, world! $
现在我们可以测试当我们要求结果时会发生什么:
main = do fut <- fork test3 n <- fut -- block until 'test3' is done print n
......也有效:
$ ./task Hello, world! 3 $
monad-par
库提供了spawn
和get
functios,可用于创建类似Future
的计算。 您既可以将Par
monad用于并行运行的纯代码,也可以将ParIO
用于带副作用的代码。
特别是,我认为您的代码示例可以转换为:
import Control.Monad.Par.IO sumPageSizesAsync :: URL -> IO ByteString sumPageSizesAsync url = runParIO $ do task <- spawn $ liftIO $ do client <- newHttpClient return $ getContents url urlContents <- get task
正如您所看到的, spawn
负责创建并行运行的代码,并返回一个IVar
,稍后可以通过get
查询它来检索其答案。 我认为这两个函数的行为非常匹配async
和await
。
有关更多信息,我建议您阅读Haskell中并行和并发编程的Par
monad章节 。