简体   繁体   English

Haskell相当于C#5 async / await

[英]Haskell equivalent of C# 5 async/await

I just read about the new way to handle asynchronous functions in C# 5.0 using the await and async keywords. 我刚刚阅读了使用awaitasync关键字在C#5.0中处理异步函数的新方法。 Examle from the C# reference on await : 等待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<byte[]> 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);
    // . . .
}

A Task<byte[]> represents the Future of an asynchronous task that will generate a value of type byte[] . Task<byte[]>表示将生成byte[]类型值的异步任务的Future。 Using the keyword await on a Task will basically put the rest of the function in a continuation which will be called when the task is done. Task上使用关键字await将基本上将函数的其余部分放在一个延续中,这将在任务完成时调用。 Any function that uses await must use the keyword async and have type Task<a> if it would return type a . 使用任何功能await必须使用关键字async和有型Task<a>是否会返回类型a

So the lines 所以线条

byte[] urlContents = await getContentsTask;
// Do something with urlContents

would translate into something like 会翻译成类似的东西

Task newTask = getContentsTask.registerContinuation(
               byte[] urlContents => {
                 // Do something with urlContents
               });
return newTask;

This feels a lot like a Monad (-transformer?). 这感觉很像Monad(变形金刚?)。 It feels like it should have some relation to the CPS monad, but maybe not. 感觉它应该与CPS monad有一些关系,但也许不是。

Here is my attempt at writing corresponding Haskell types 这是我尝试编写相应的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

and a rough translation of the above example 以及上述例子的粗略翻译

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]

    -- ...

Would this be the corresponding types in Haskell? 这是Haskell中的相应类型吗? Is there any Haskell library this (or a similar way) implements way to handle asynchronous functions/actions? 是否有任何Haskell库(或类似方式)实现处理异步函数/操作的方法?

Also: Could you build this using the CPS-transformer? 另外:你能用CPS变压器构建它吗?

Edit 编辑

Yes, the Control.Concurrent.Async module does solve a similar problem (and has a similar interface), but does so in an entirely different way. 是的, Control.Concurrent.Async模块确实解决了类似的问题(并且具有类似的界面),但是以完全不同的方式这样做。 I guess that Control.Monad.Task would be a closer match. 我猜Control.Monad.Task会更接近匹配。 What (I think) I am looking for is a monadic interface for Futures that uses Continuation Passing Style behind the scenes . 我想要的是Futures的monadic界面,它在幕后使用Continuation Passing Style

Here's a Task monad that builds on top of the async library: 这是一个构建在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 (f a)
        bFut

Note that I haven't checked the monad laws for this, so it might not be correct. 请注意,我没有检查monad法律,所以它可能不正确。

This is how you would define primitive tasks that run in the background: 这是您定义在后台运行的原始任务的方法:

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

Then you can combine Task s using do notation which creates a new deferred task ready to be run: 然后,您可以使用do notation组合Task ,这会创建一个准备好运行的新延迟任务:

test3 :: Task Int
test3 = do
    n1 <- test1
    n2 <- test2
    return (n1 + n2)

Running fork test3 will spawn the Task and return a future which you can invoke at any time to demand the result, blocking if necessary until done. 运行fork test3将生成Task并返回一个可以随时调用的未来以请求结果,必要时阻塞直到完成。

To show that it works, I'll do two simple tests. 为了证明它有效,我将做两个简单的测试。 First, I'll fork test3 without demanding its future just to make sure it spawns the composite thread correctly: 首先,我将fork test3而不要求它的未来只是为了确保它正确生成复合线程:

main = do
    fork test3
    getLine -- wait without demanding the future

This works correctly: 这工作正常:

$ ./task
Hello,
 world!
<Enter>
$

Now we can test what happens when we demand the result: 现在我们可以测试当我们要求结果时会发生什么:

main = do
    fut <- fork test3
    n   <- fut  -- block until 'test3' is done
    print n

... which also works: ......也有效:

$ ./task
Hello,
 world!
3
$

The monad-par library provides spawn and get functios that can be used to create Future -like computations. monad-par库提供了spawnget函数,可用于创建类似Future的计算。 You can either use the Par monad for pure code that would be run in parallel, or ParIO for code with side-effects. 您既可以将Par monad用于并行运行的纯代码,也可以将ParIO用于带副作用的代码。

In particular, I think that you're code example could be translated into: 特别是,我认为您的代码示例可以转换为:

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

As you can see, spawn is the responsible for creating code that runs in parallel, and returns an IVar which can be later on queried by get for retrieving its answer. 正如您所看到的, spawn负责创建并行运行的代码,并返回一个IVar ,稍后可以通过get查询它来检索其答案。 I think the behavior of those two functions match very well async and await . 我认为这两个函数的行为非常匹配asyncawait

For more information I recommend you reading the Par monad chapter of Parallel and Concurrent Programming in Haskell . 有关更多信息,我建议您阅读Haskell并行和并发编程Par monad章节

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM