Haskell相当于C#5 async / await

我刚刚阅读了使用awaitasync关键字在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库提供了spawnget 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查询它来检索其答案。 我认为这两个函数的行为非常匹配asyncawait

有关更多信息,我建议您阅读Haskell并行和并发编程Par monad章节 。