在异步结果可用时收集它们

时间:2016-08-07 10:02:48

标签: multithreading haskell asynchronous

如何收集Haskell Async a列表的结果,因为它们可用 ?我们的想法是在异步任务可用时立即开始处理它们。

我能想到的最好的是以下功能:

collect :: [Async a] -> IO [a]
collect [] = return []
collect asyncs = do
  (a, r) <- waitAny asyncs
  rs <- collect (filter (/= a) asyncs)
  return (r:rs)

但是,此函数没有表现出所需的行为,因为正如下面的注释所指出的那样,它在所有异步任务完成之前不会返回。此外,collectO(n^2)中运行,因为我在每个递归步骤中过滤列表。这可以通过使用更有效的结构(并且可能索引列表中Async值的位置)来改进。

也许有库函数可以解决这个问题,但我在Control.Concurrent.Async模块中找不到它们,我想知道为什么。

编辑 :在仔细考虑问题之后,我想知道这样的功能是否是一个好主意。我可以在异步任务上使用fmap。当没有其他选择时,等待结果可能是更好的做法。

3 个答案:

答案 0 :(得分:1)

正如我在my other answer中提到的,使用流处理库可以最好地实现流Async列表中的流式传输结果。以下是使用pipes的示例。

import Control.Concurrent (threadDelay)
import Control.Concurrent.Async
import Control.Concurrent.STM
import Data.Functor (($>))
import Pipes
import Pipes.Concurrent  -- from the pipes-concurrency package
import qualified Pipes.Prelude as P


asCompleted :: MonadIO m => [Async a] -> Producer a m ()
asCompleted asyncs = do
    (o, i, seal) <- liftIO $ spawn' unbounded
    liftIO $ forkIO $ do
        forConcurrently asyncs (\async -> atomically $ waitSTM async >>= send o)
        atomically seal
    fromInput i

main = do
    actions <- traverse async [threadDelay 2000000 $> "bar", threadDelay 1000000 $> "foo"]
    runEffect $ asCompleted actions >-> P.print
-- after one second, prints "foo", then "bar" a second later

使用pipes-concurrency,我们spawn' Output - Input对,并立即使用{{3}将Input转换为Producer }。异步地,我们fromInput项可用。完成所有Async后,我们会seal收件箱关闭Producer

答案 1 :(得分:1)

通过TChan实现,另外实现了一个可以立即做出反应的版本,但它更复杂,也可能有异常问题(如果您想接收异常,请使用SlaveThread.fork代替{{ 1}}),所以我评论了代码,以防你对它不感兴趣:

forkIO

答案 2 :(得分:0)

我正在阅读您的问题“是否可以按完成时间对conversationsFetchRequest.predicate = NSPredicate(format: "SUBQUERY(messages, %x, %x.triggeredNotification == false)") 列表进行排序?”。如果这就是你的意思,答案是肯定的。

Async

使用import Control.Applicative (liftA2) import Control.Concurrent (threadDelay) import Control.Concurrent.Async import Data.Functor (($>)) import Data.List (sortBy) import Data.Ord (comparing) import Data.Time (getCurrentTime) sortByCompletion :: [Async a] -> IO [a] sortByCompletion = fmap (fmap fst . sortBy (comparing snd)) . mapConcurrently withCompletionTime where withCompletionTime async = liftA2 (,) (wait async) getCurrentTime main = do asyncs <- traverse async [threadDelay 2000000 $> "bar", threadDelay 1000000 $> "foo"] sortByCompletion asyncs -- ["foo", "bar"], after two seconds 我们在单独的线程上等待每个mapConcurrently。完成后,我们得到当前时间 - Async完成的时间 - 并使用它来对结果进行排序。这是O(n log n)复杂度,因为我们正在对列表进行排序。 (您的原始算法实际上是selection sort。)

与您的Async一样,collect只有在列表中的所有sortByCompletion完成后才会返回。如果你希望结果发送到主线程上,那么列表就不是一个非常好的工具。我会使用像conduitpipes这样的流式抽象,或者在较低级别使用TQueue。有关示例,请参阅my other answer