This module provides a set of operations for running IO operations
asynchronously and waiting for their results. It is a thin layer
over the basic concurrency operations provided by
Control.Concurrent. The main additional functionality it
provides is the ability to wait for the return value of a thread,
but the interface also provides some additional safety and
robustness over using forkIO threads and MVar directly.
High-level API
async's high-level API spawns lexically scoped threads,
ensuring the following key properties that make it safer to use
than using plain forkIO:
No exception is swallowed (waiting for results propagates exceptions).
No thread is leaked (left running unintentionally).
(This is done using the bracket pattern to work in presence
of synchronous and asynchronous exceptions.)
Most practical/production code should only use the high-level API.
The basic type is Async a, which represents an asynchronous
IO action that will return a value of type a, or die with an
exception. An Async is a wrapper around a low-level forkIO thread.
The fundamental function to spawn threads with the high-level API is
withAsync.
For example, to fetch two web pages at the same time, we could do
this (assuming a suitable getURL function):
withAsync (getURL url1) $ \a1 -> do
withAsync (getURL url2) $ \a2 -> do
page1 <- wait a1
page2 <- wait a2
...
where withAsync starts the operation in a separate thread, and
wait waits for and returns the result.
If the operation throws an exception, then that exception is re-thrown
by wait. This ensures property (1): No exception is swallowed.
If an exception bubbles up through a withAsync, then the Async
it spawned is canceled. This ensures property (2): No thread is leaked.
Often we do not care to work manually with Async handles like
a1 and a2. Instead, we want to express high-level objectives like
performing two or more tasks concurrently, and waiting for one or all
of them to finish.
For example, the pattern of performing two IO actions concurrently and
waiting for both their results is packaged up in a combinator concurrently,
so we can further shorten the above example to:
(page1, page2) <- concurrently (getURL url1) (getURL url2)
...
The section High-level utilities covers the most
common high-level objectives, including:
Waiting for 2 results (concurrently).
Waiting for many results (mapConcurrently / forConcurrently).
Waiting for the first of 2 results (race).
Waiting for arbitrary nestings of "all of N" and "the first of N"
results with the Concurrently newtype and its Applicative and
Alternative instances.
Click here to scroll to that section:
"Control.Concurrent.Async#high-level-utilities".
Low-level API
Some use cases require parallelism that is not lexically scoped.
For those, the low-level function async can be used as a direct
equivalent of forkIO:
-- Do NOT use this code in production, it has a flaw (explained below).
do
a1 <- async (getURL url1)
a2 <- async (getURL url2)
page1 <- wait a1
page2 <- wait a2
...
In contrast to withAsync, this code has a problem.
It still fulfills property (1) in that an exception arising from
getUrl will be re-thrown by wait, but it does not fulfill
property (2).
Consider the case when the first wait throws an exception; then the
second wait will not happen, and the second async may be left
running in the background, possibly indefinitely.
withAsync is like async, except that the Async is
automatically killed (using uninterruptibleCancel) if the
enclosing IO operation returns before it has completed.
Furthermore, withAsync allows a tree of threads to be built, such
that children are automatically killed if their parents die for any
reason.
If you need to use the low-level API, ensure that you guarantee
property (2) by other means, such as linking asyncs that need
to die together, and protecting against asynchronous exceptions
using bracket, mask,
or other functions from Control.Exception.
Miscellaneous
The Functor instance can be used to change the result of an
Async. For example:
ghci> withAsync (return 3) (\a -> wait (fmap (+1) a))
4
Resource exhaustion
As with all concurrent programming, keep in mind that while
Haskell's cooperative ("green") multithreading carries low overhead,
spawning too many of them at the same time may lead to resource exhaustion
(of memory, file descriptors, or other limited resources), given that the
actions running in the threads consume these resources.