如何收集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)
但是,此函数没有表现出所需的行为,因为正如下面的注释所指出的那样,它在所有异步任务完成之前不会返回。此外,collect
在O(n^2)
中运行,因为我在每个递归步骤中过滤列表。这可以通过使用更有效的结构(并且可能索引列表中Async
值的位置)来改进。
也许有库函数可以解决这个问题,但我在Control.Concurrent.Async
模块中找不到它们,我想知道为什么。
编辑 :在仔细考虑问题之后,我想知道这样的功能是否是一个好主意。我可以在异步任务上使用fmap
。当没有其他选择时,等待结果可能是更好的做法。
答案 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
完成后才会返回。如果你希望将结果发送到主线程上,那么列表就不是一个非常好的工具。我会使用像conduit
或pipes
这样的流式抽象,或者在较低级别使用TQueue
。有关示例,请参阅my other answer。