Haskell相当于C#5 async/await

Hju*_*lle 11 c# haskell asynchronous

我刚刚阅读了使用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<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);
    // . . .
}
Run Code Online (Sandbox Code Playgroud)

A Task<byte[]>表示将生成类型值的异步任务的Future byte[].await在a上使用关键字Task将基本上将函数的其余部分放在一个延续中,这将在任务完成时调用.任何await使用的函数都必须使用关键字async,Task<a>如果它返回类型则具有类型a.

所以线条

byte[] urlContents = await getContentsTask;
// Do something with urlContents
Run Code Online (Sandbox Code Playgroud)

会翻译成类似的东西

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

这感觉很像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
Run Code Online (Sandbox Code Playgroud)

以及上述例子的粗略翻译

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]

    -- ...
Run Code Online (Sandbox Code Playgroud)

这是Haskell中的相应类型吗?是否有任何Haskell库(或类似方式)实现处理异步函数/操作的方法?

另外:你能用CPS变压器构建它吗?

编辑

是的,该Control.Concurrent.Async模块确实解决了类似的问题(并且具有类似的界面),但是以完全不同的方式这样做.我猜这Control.Monad.Task将是一场更接近的比赛.我想要的是Futures的monadic界面,它在幕后使用Continuation Passing Style.

Gab*_*lez 9

这是一个Task构建在async库之上的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
Run Code Online (Sandbox Code Playgroud)

请注意,我没有检查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
Run Code Online (Sandbox Code Playgroud)

然后,您可以Task使用do符号来组合s ,这会创建一个准备运行的新延迟任务:

test3 :: Task Int
test3 = do
    n1 <- test1
    n2 <- test2
    return (n1 + n2)
Run Code Online (Sandbox Code Playgroud)

运行fork test3将生成Task并返回一个您可以随时调用的未来,以请求结果,必要时阻止直到完成.

为了证明它有效,我将做两个简单的测试.首先,我将分叉test3而不要求它的未来,只是为了确保它正确生成复合线程:

main = do
    fork test3
    getLine -- wait without demanding the future
Run Code Online (Sandbox Code Playgroud)

这工作正常:

$ ./task
Hello,
 world!
<Enter>
$
Run Code Online (Sandbox Code Playgroud)

现在我们可以测试当我们要求结果时会发生什么:

main = do
    fut <- fork test3
    n   <- fut  -- block until 'test3' is done
    print n
Run Code Online (Sandbox Code Playgroud)

......也有效:

$ ./task
Hello,
 world!
3
$
Run Code Online (Sandbox Code Playgroud)

  • 虽然我认为未来的类型是monad,但你误解了await运算符在C#中的作用.等待点不是"分叉同时执行此计算的新线程"!等待点是它意味着"将当前方法重写为连续传递样式;评估当前任务返回表达式,记录该任务中的当前延续,并将控制返回给调用者".任务完成后,将安排继续.这与你所写的完全不同. (3认同)