Permalink
Browse files

Initial version of F# async extensions and samples

  • Loading branch information...
1 parent da2b1f5 commit 05dbe9ab4f19e7f0506a908d53cd7942b31fe375 @tpetricek committed Aug 11, 2011
View
@@ -0,0 +1,4 @@
+*.dll
+*.pdb
+FSharp.ASyncExtensions.xml
+bin/release
View
@@ -0,0 +1,114 @@
+F# Async Extensions
+===================
+
+This library implements various extensions for asynchronous programming
+using F# asynchronous workflows and F# agents (the `MailboxProcessor` type
+in the standard F# library). It defines _asynchronous sequences_ that represent
+asynchronous operations returning multiple values (such as reading data from
+a stream in chunks), several reusable F# agents and numerous extensions.
+
+ * Samples that demonstrate how to use most of the extensions can
+ be found in the [samples directory][7]
+
+Asynchronous sequences
+----------------------
+
+Asynchronous sequences can be used to work with asynchronous computations that return
+multiple results. A value of type `AsyncSeq<'T>` can be started (just like an asynchronous
+workflow) and it eventually returns. The result is either a special value representing
+the end of the sequence or a value of type `'T` (head) together with the rest of the
+asynchronous sequence (tail) of type `AsyncSeq<'T>`.
+
+Unlike `IObservable<'T>`, asynchronous sequences are not _push-based_. The code that
+generates the next value of the asynchronous sequence starts only after previous elements
+have been processed. This makes it possible to easily write computations that return
+results as long as some component is using them.
+
+However, `IObservable<'T>` values can
+be converted to asynchronous sequences. The `AsyncSeq.ofObservable` combinator creates an
+asynchronous sequence that discards values produced by the observable while the
+asynchronous sequence was blocked. The `AsyncSeq.ofObservableBuffered` combinator stores
+all produced values in an unbounded buffer and returns the values from the buffer as soon
+as the user of asynchronous sequence requestst the next element.
+
+The library defines an F# computation expression for workfing with asynchronous sequences.
+For example, sequence that emits numbers in 1 second intervals can be defined as follows:
+
+ let rec numbers n = asyncSeq {
+ yield n
+ do! Async.Sleep(1000)
+ yield! numbers (n + 1) }
+
+Asynchronous workflows and asynchronous sequences can use the `for` construct to iterate
+over all elements of an asynchronous sequence. For example:
+
+ let rec evenNumbers = asyncSeq {
+ for n in numbers 0 do
+ if n%2=0 then yield n }
+
+The library also provides numerous combinators (similar to functions from the `Seq` module).
+The result of operations that aggregate values of an asynchronous sequence is an asynchronous
+workflow that returns a single value:
+
+ let rec sumTenEvenSquares =
+ numbers 0
+ |> AsyncSeq.filter (fun n -> n%2 = 0)
+ |> AsyncSeq.map (fun n -> n*n)
+ |> AsyncSeq.fold (+) 0
+
+ let n =
+ sumTenEvenSquares
+ |> Async.RunSynchronously
+
+For some examples that use (earlier versions) of asynchronous sequences, see also the following
+two F# snippets: [first][5] and [second][6].
+
+Reusable agents
+---------------
+
+The library implements several reusable agents for building concurrent applications:
+
+ * **Agent** is a simple type aliast for `MailboxProcessor` that is more convenient to use
+
+ * **AutoCancelAgent** wraps the standard F# agent and adds support for stopping of the
+ agent's body using the `IDisposable` interface (the type automatically creates a
+ cancellation token, uses it to start the underlying agent and cancels it when the agent
+ is disposed). For example, [see this F# snippet][1].
+
+ * **BatchProcessingAgent** can be used to implement batch processing. It creates groups of
+ messages (added using the `Enqueue` method) and emits them using the `BatchProduced`
+ event. A group is produced when it reaches the maximal size or after the timeout elapses.
+
+ * **BlockingQueueAgent** implements an asynchronous queue with blocking put and blocking
+ get operations. It can be used to implement the _producer-consumer_ concurrent pattern.
+ The constructor of the agent takes the maximal size of the buffer.
+
+
+Observable extensions
+---------------------
+
+The library implements extensions for using `IObservable<'T>` type from F# asynchronous
+workflows. An overloaded extension method `Async.AwaitObservable` can be used to wait
+for an occurrence of an event (or other observable action):
+
+ let counter n = async {
+ printfn "Counting: %d" n
+ let! _ = Async.AwaitEvent(form.MouseDown)
+ return! counter (n + 1) }
+
+Overloaded version of the method allows waiting for the first of multiple events. The
+method asynchronously returns `Choice<'T1, 'T2>` value that can be used to determine
+which of the events has occurred.
+
+For examples using this method see Chapter 16 of [Real World Functional Programming][2]
+(some examples are available in a [free excerpt from the chapter][3]). The
+`Async.AwaitObservable` method should be used instead of `Async.AwaitEvent` to avoid
+memory leaks (see also related [StackOverflow discussion][4])
+
+ [1]: http://fssnip.net/64
+ [2]: http://manning.com/petricek
+ [3]: http://dotnetslackers.com/articles/net/Programming-user-interfaces-using-f-sharp-workflows.aspx
+ [4]: http://stackoverflow.com/questions/3701861/wait-for-any-event-of-multiple-events-simultaneously-in-f
+ [5]: http://fssnip.net/1k
+ [6]: http://fssnip.net/1Y
+ [7]: https://github.com/tpetricek/FSharp.AsyncExtensions
View
Binary file not shown.
@@ -0,0 +1,54 @@
+// ----------------------------------------------------------------------------
+// F# async extensions (AsyncSeqObservable.fsx)
+// (c) Tomas Petricek, 2011, Available under Apache 2.0 license.
+// ----------------------------------------------------------------------------
+
+// This example demonstrates how to convert IObservable<'T> to AsyncSeq<'T>
+
+#r "..\\bin\\FSharp.AsyncExtensions.dll"
+open FSharp.Control
+open System.Windows.Forms
+open System.Threading
+
+// Create simple winforms user interface with a button and multiline text box
+let frm = new Form(Visible=true, TopMost=true, Width=440)
+let btn = new Button(Left=10, Top=10, Width=150, Text="Async Operation")
+let out = new TextBox(Left=10, Top=40, Width=400, Height=200, Multiline=true)
+frm.Controls.Add(btn)
+frm.Controls.Add(out)
+
+// Prints message to the displayed text box
+let wprint fmt =
+ Printf.kprintf (fun s -> out.Text <- out.Text + s) fmt
+
+
+// The sample demonstrates two ways of converting IObservable<_> values to
+// asynchronous sequences. When using 'AsyncSeq.ofObservable', values that are
+// emitted when the asynchronous sequence is blocked are discarded. When you
+// click on the 'Async Operation' button, the following workflow starts
+// processing and drops all clicks until the body of the for loop completes
+let discarding =
+ async {
+ for click in btn.Click |> AsyncSeq.ofObservable do
+ wprint "Sleeping (and discarding clicks)...\r\n"
+ do! Async.Sleep(1000)
+ wprint "Done (listening again)\r\n" }
+
+let ctsd = new CancellationTokenSource()
+Async.Start(discarding, ctsd.Token)
+ctsd.Cancel()
+
+
+// When using 'AsyncSeq.ofObservableBuffered', the values emitted by the
+// observable while the asynchronous sequence is blocked are stored in a
+// buffer (and will be returned as next elements).
+let buffering =
+ async {
+ for click in btn.Click |> AsyncSeq.ofObservableBuffered do
+ wprint "Sleeping (and buffering clicks)...\r\n"
+ do! Async.Sleep(1000)
+ wprint "Done (ready for next value)\r\n" }
+
+let ctsb = new CancellationTokenSource()
+Async.Start(buffering, ctsb.Token)
+ctsb.Cancel()
View
@@ -0,0 +1,39 @@
+// ----------------------------------------------------------------------------
+// F# async extensions (AutoCancel.fsx)
+// (c) Tomas Petricek, 2011, Available under Apache 2.0 license.
+// ----------------------------------------------------------------------------
+
+// This example demonstrates how to use 'AutoCancelAgent'
+// The agent automatically stops its body when disposed.
+
+#r "..\\bin\\FSharp.AsyncExtensions.dll"
+open FSharp.Control
+
+let op = async {
+ // Create a local agent that is disposed when the
+ // workflow completes (using the 'use' construct)
+ use agent = AutoCancelAgent.Start(fun agent -> async {
+ try
+ while true do
+ // Wait for a message - note that we use timeout
+ // to allow cancellation (when the operation completes)
+ let! msg = agent.Receive(1000)
+ match msg with
+ | (n, reply:AsyncReplyChannel<unit>) ->
+ // Print number and reply to the sender
+ printfn "%d" n
+ reply.Reply(())
+ | _ -> ()
+ finally
+ // Called when the agent is disposed
+ printfn "agent completed" })
+
+ // Do some processing using the agent...
+ for i in 0 .. 10 do
+ do! Async.Sleep(100)
+ do! agent.PostAndAsyncReply(fun r -> i, r)
+
+ do! Async.Sleep(100)
+ printfn "workflow completed" }
+
+Async.Start(op)
@@ -0,0 +1,31 @@
+// ----------------------------------------------------------------------------
+// F# async extensions (BatchProcessing.fsx)
+// (c) Tomas Petricek, 2011, Available under Apache 2.0 license.
+// ----------------------------------------------------------------------------
+
+// This example demonstrates how to use 'BatchProcessingAgent'
+// The agent groups received messages in groups with a maximal
+// size and emits them with a maximal timeout.
+
+#r "..\\bin\\FSharp.AsyncExtensions.dll"
+open FSharp.Control
+
+open System.Drawing
+open System.Windows.Forms
+
+// Create simple winforms user interface with label
+let frm = new Form()
+let lbl = new Label(Font = new Font("Calibri", 20.0f), Dock = DockStyle.Fill)
+lbl.TextAlign <- ContentAlignment.MiddleCenter
+frm.Controls.Add(lbl)
+frm.Show()
+
+// Handle key press events but update the GUI after 5 keys
+// have been pressed or after 5 seconds (whichever happens first)
+let ag = new BatchProcessingAgent<_>(5, 5000)
+frm.KeyPress.Add(fun e -> ag.Enqueue(e.KeyChar))
+ag.BatchProduced
+ |> Event.map (fun chars -> new System.String(chars))
+ |> Event.scan (+) ""
+ |> Event.add (fun str -> lbl.Text <- str)
+
View
@@ -0,0 +1,35 @@
+// ----------------------------------------------------------------------------
+// F# async extensions (BlockingQueue.fsx)
+// (c) Tomas Petricek, 2011, Available under Apache 2.0 license.
+// ----------------------------------------------------------------------------
+
+// This example demonstrates how to use 'BlockingAgent'
+// The agent implements producer/consumer concurrent pattern.
+
+#r "..\\bin\\FSharp.AsyncExtensions.dll"
+open FSharp.Control
+
+let buffer = new BlockingQueueAgent<int>(3)
+
+// The sample uses two workflows that add/take elements
+// from the buffer with the following timeouts. When the producer
+// timout is larger, consumer will be blocked. Otherwise, producer
+// will be blocked.
+let producerTimeout = 500
+let consumerTimeout = 1000
+
+async {
+ for i in 0 .. 10 do
+ // Sleep for some time and then add value
+ do! Async.Sleep(producerTimeout)
+ do! buffer.AsyncAdd(i)
+ printfn "Added %d" i }
+|> Async.Start
+
+async {
+ while true do
+ // Sleep for some time and then get value
+ do! Async.Sleep(consumerTimeout)
+ let! v = buffer.AsyncGet()
+ printfn "Got %d" v }
+|> Async.Start
View
@@ -0,0 +1,33 @@
+// ----------------------------------------------------------------------------
+// F# async extensions (AutoCancel.fsx)
+// (c) Tomas Petricek, 2011, Available under Apache 2.0 license.
+// ----------------------------------------------------------------------------
+
+// This example demonstrates how to use 'Async.Cache' and 'AsyncSeq.cache'
+
+#r "..\\bin\\FSharp.AsyncExtensions.dll"
+open FSharp.Control
+
+// The Async.Cache combinator makes it possible to create asynchronous
+// workflow that caches the result and performs computation only once
+// when called multiple times.
+let op =
+ async { // Will be printed just once
+ printfn "Evaluating..."
+ return 42 }
+ |> Async.Cache
+
+Async.RunSynchronously(op)
+Async.RunSynchronously(op)
+
+
+// The AsyncSeq.cache combinator has similar effect - the asynchronous
+// sequence can be used multiple times, but it will evaluate just once
+let asq =
+ asyncSeq { for i in 0 .. 10 do
+ printfn "Generating %d..." i
+ yield i }
+ |> AsyncSeq.cache
+
+AsyncSeq.iter (printfn "Consuming %d") asq
+|> Async.RunSynchronously
Oops, something went wrong.

0 comments on commit 05dbe9a

Please sign in to comment.