TP

Writing non-blocking user-interfaces in F#

F# asynchronous workflows are best known as a way to write efficient I/O operations or as an underlying mechanism of F# agent-based programming (using the MailboxProcessor type). However, they are also very useful for user-interface programming. I think this is a very interesting and important area, so I already wrote and talked about this topic - it is covered in Chapter 16 of my book (there is a free excerpt) and I talked about it at F#unctional Londoners meeting.

Many applications combine user-interface programming (such as waiting for an event asynchronously) with some CPU-intensive tasks. This article looks at an example of such application and I'll explain how to avoid blocking the user-interface when doing the CPU-intensive task. The article starts with an example that is wrong and blocks the user-interface when doing data processing. Then I'll show you two options for fixing the problem. The three most important functions from the standard F# library that I'll discuss are Async.StartChild and Async.SwitchTo­ThreadPool with Async.SwitchToContext.

This is the first article of a mini-series. In the next article, I'll demonstrate a simple wrapper for F# async that makes it more difficult to write wrong programs. The wrapper keeps the desired thread (GUI or background) in the type of the computations and code that would block the user interface will not type-check. But first, let's look at the example...

Showing stock prices in Silverlight

The sample application is shown in a screenshot above (and you can get complete source code from GitHub link at the end of the article). It consists of a client-side (Silverlight) application and simple server-side proxy that returns stock prices from Yahoo Finance. The client-side application generates buttons for a few stocks. When a button is clicked, the application connects to the server and downloads the data (asynchronously). Then it does some CPU-intensive processing (currently just parse the data and then call Thread.Sleep for some time). Finally, it generates a chart and adds it to the user-interface.

Processing data and creating charts

Looking at the complete source is beyond the scope of the article, but this section shows the two most important helper functions that are both wrapped inside an asynchronous workflow. The extractPrices function gets the input data (as a string), does some processing and returns the result as an array of floats. It is CPU-bounded, so it will block the executing thread for some time. To make this more obvious, I added Thread.Sleep (Note that Async.Sleep would behave differently, because that wouldn't block the thread).

The displayChart function is quite simple. It constructs user interface (using createChart not shown in the snippet) and then adds it to some existing Canvas object. The function is also written as an asynchronous workflow. This is not necessary now, but it will make more sense in the next article.

 1: /// Parse downloaded data set. This is a CPU-expensive 
 2: /// computation that should not block the user-interface.
 3: let extractStockPrices (data:string) count = async {
 4:   let dataLines = (...)
 5:   let data = (...)
 6:   // Additional CPU-bound data processing
 7:   Thread.Sleep(5000)
 8:   return data }
 9: 
10: /// Create & display chart. The function needs to 
11: /// access user interface and should run on GUI thread.
12: let displayChart color prices = async {
13:   let chart = createChart 600.0 200.0 color prices
14:   chart.SetValue(Canvas.LeftProperty, 100.0)
15:   chart.SetValue(Canvas.TopProperty, 100.0)
16:   holder.Children.Clear()
17:   holder.Children.Add(chart) }

The snippet shows fairly simple F# code. However, the important point is made in the comments of the two functions. The extractStockPrices function should be executed on a background thread, because it blocks the thread and we don't want to make the user-interface non-responsive. The displayChart function must be executed on the GUI thread, because it accesses user-interface elements, which is only allowed from the GUI thread.

The desired threading model is an important thing to keep in mind when using asynchronous workflows. Of course, when this is written just as a comment, it is easy to make a mistake. In the next article, I'll show how to correct that and put this information into the type of the computation. First, let's look at the main workflow of the application that calls both functions from the GUI thread.

Running the application

The application is implemented as an asynchronous workflow that contains a while loop that repeatedly waits for the user input (clicking on a button) and performs some reaction (download & process data and display chart). The body of the loop waits for the mouse click using AwaitObservable. After that, it downloads the data using AsyncDownloadString and then calls the two helper functions from the previous section. As mentioned earlier, this version is wrong, because it runs the CPU-intensive function extractStockPrices on the GUI thread. This means that it hangs the user-interface. The rest of the article shows two ways to fix this.

 1: let main = async {
 2:   // Construct event that occurs when stock is selected
 3:   let stockClicked =
 4:     [ for stock, btn in Seq.zip stocks buttons ->
 5:         btn.Click |> Observable.map (fun _ -> stock) ]
 6:     |> List.reduce Observable.merge
 7:   
 8:   // The main application loop
 9:   while true do 
10:     // Wait until the user selects stock (GUI operation)
11:     let! color, stock = Async.AwaitObservable(stockClicked)
12:     // Download data for stock (non-blocking operation)
13:     let wc = new WebClient()
14:     let! data = wc.AsyncDownloadString(Uri(root + stock))    
15:     // Process data (CPU-bound operation)
16:     let! prices = extractStockPrices data 500
17:     // Create & display the chart (GUI operation)
18:     do! displayChart color prices }
19: 
20: // Start the workflow immediately (on GUI thread)
21: do main |> Async.StartImmediate

The construction of stockClicked event is quite interesting. It uses two collections containing names of stocks with associated color (stocks) and button objects (buttons). Then it zips them and generates a list of events IObservable<Color * string>. Finally, it uses List.reduce with Observable.merge as an argument to create a single event that occurs whenever any of the events occur.

The rest of the workflow in the while loop is quite straightforward. The workflow is started (on the GUI thread) using the Async.StartImmedaite function and all of the asynchronous operations return back to the GUI thread. In Silverlight, WebClient automatically behaves like this, but the F# wrappers for other asynchronous operations guarantee the same behavior. This means that any part of the workflow can safely access the user-interface, but calling a CPU-intensive operation in the workflow causes the application to hang. To correct this, we need to call extractStockPrices function differently...

Processing data in background

The standard F# library provides two options for running an asynchronous workflow without blocking the current thread. One option is to start a workflow as a child (on another thread) and asynchronously wait until it completes. The second option is to switch the current workflow to another thread, do the heavy computation and then switch back. The following examples show both of the options.

Starting child workflow

The Async.StartChild operation has a type Async<'T> -> Async<Async<'T>>. It creates a new asynchronous workflow that, when started, runs the work on some background thread and returns a "token" (of type Async<'T>) that can be used to wait until the operation completes. This waiting is done without blocking the current thread, so it is safe to do it on the GUI thread. The main workflow of the application can be written like this:

 1: let main = async {
 2:   (Initialization omitted)
 3: 
 4:   // The main application loop
 5:   while true do 
 6:     // Wait until the user selects stock (GUI operation)
 7:     let! color, stock = Async.AwaitObservable(stockClicked)
 8:     // Download data for stock (non-blocking operation)
 9:     let wc = new WebClient()
10:     let! data = wc.AsyncDownloadString(Uri(root + stock))    
11:     
12:     // Process data (CPU-bound operation)
13:     let! token = Async.StartChild(extractStockPrices data 500)
14:     let! prices = token
15: 
16:     // Create & display the chart (GUI operation)
17:     do! displayChart color prices }

The only change is on the lines 13 and 14. The first line starts the computation as a child (in the background) by using let! on the result of StartChild. This completes immediately and the workflow can then continue doing some other work (e.g. spawn more background tasks to implement parallelism). The example in this article doesn't need to do anything else - it just needs to (asynchronously) wait until the task completes, which is done using the second let! construct.

The StartChild operation from the previous section can be used to implement parallelism. It can be used (by using two subsequent let!) to delegate long-running CPU-intensive computations to another thread, but that's probably not the main use.

Switching between threads

The other way to avoid running a part of the workflow on the current (GUI) thread is to switch from the GUI thread to a background thread and then back. This can be done using two standard operations of the Async type, namely SwitchToThreadPool and SwitchToContext. The only thing that the operations do is that they resume the workflow (run the continuation) on a different thread. The following snippet shows how to use the two operations to rewrite the main workflow of the example:

 1: let main = async {
 2:   (Initialization omitted)
 3: 
 4:   // The main application loop
 5:   while true do 
 6:     // Wait until the user selects stock (GUI operation)
 7:     let! color, stock = Async.AwaitObservable(stockClicked)
 8:     // Download data for stock (non-blocking operation)
 9:     let wc = new WebClient()
10:     let! data = wc.AsyncDownloadString(Uri(root + stock))    
11: 
12:     // Process data (CPU-bound operation)
13:     let ctx = SynchronizationContext.Current
14:     do! Async.SwitchToThreadPool()
15:     let! prices = extractStockPrices data 500
16:     do! Async.SwitchToContext(ctx)
17: 
18:     // Create & display the chart (GUI operation)
19:     do! displayChart color prices }

The new behavior is implemented on lines 13 - 16. The workflow first needs to store the current synchronization context, which is obtained using SynchronizationContext.Current while the workflow still runs on the main GUI thread. Next, the snippet calls the SwitchToThreadPool operation. The return type of the method is Async<unit>, which means that it can be called using the do! syntax. The result of the call is that the rest of the workflow (starting on line 15) is executed on a thread pool thread. Then the snippet can safely run the CPU-intensive processing using extractStockPrices. Once the CPU-intensive computation completes, the workflow switches back to the GUI thread using SwitchToContext with the original context as an argument.

Summary

The approach based on switching is slightly longer, but it has an interesting advantage - the values that were declared on another thread (line 15) are still available after returning back to the original thread. This is safe, because the entire computation is sequential, so the variables are always accessed from just a single thread. When using Async.StartChild, all results need to be explicitly returned as the result of the child workflow and then assigned to some variables (using let!), so the code can become more complex.

One possible disadvantage of the second approach is that it is easier to make a mistake. If we forgot the call to Async.SwitchToContext (line 16) then the program would compile, but it would fail at runtime, because it would attempt to access the user-interface elements (in the displayChart function) from a thread-pool thread. The Async<'T> can be extended to catch this kind of errors at compile time and I'll cover that in the next article.

Downloads & Source code

val extractStockPrices : string -> int -> Async<float []>

Full name: Demo.StandardAsync.extractStockPrices

Parse downloaded data set. This is a CPU-expensive
 computation that should not block the user-interface.

val data : string

  type: string
  implements: IComparable
  implements: ICloneable
  implements: IConvertible
  implements: IComparable<string>
  implements: seq<char>
  implements: Collections.IEnumerable
  implements: IEquatable<string>
Multiple items
val string : 'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------

type string = String

Full name: Microsoft.FSharp.Core.string

  type: string
  implements: IComparable
  implements: ICloneable
  implements: IConvertible
  implements: IComparable<string>
  implements: seq<char>
  implements: Collections.IEnumerable
  implements: IEquatable<string>
val count : int

  type: int
  implements: IComparable
  implements: IFormattable
  implements: IConvertible
  implements: IComparable<int>
  implements: IEquatable<int>
  inherits: ValueType
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val dataLines : string []

  type: string []
  implements: ICloneable
  implements: Collections.IList
  implements: Collections.ICollection
  implements: Collections.IStructuralComparable
  implements: Collections.IStructuralEquatable
  implements: Collections.Generic.IList<string>
  implements: Collections.Generic.ICollection<string>
  implements: seq<string>
  implements: Collections.IEnumerable
  inherits: Array
data.Split([| '\n' |], StringSplitOptions.RemoveEmptyEntries)
val data : float []

  type: float []
  implements: ICloneable
  implements: Collections.IList
  implements: Collections.ICollection
  implements: Collections.IStructuralComparable
  implements: Collections.IStructuralEquatable
  implements: Collections.Generic.IList<float>
  implements: Collections.Generic.ICollection<float>
  implements: seq<float>
  implements: Collections.IEnumerable
  inherits: Array
[| for line in dataLines |> Seq.skip 1 do
     let infos = line.Split(',')
     yield float infos.[1] |]
|> Seq.take count |> Array.ofSeq |> Array.rev
type Thread =
  class
    inherit System.Runtime.ConstrainedExecution.CriticalFinalizerObject
    new : System.Threading.ThreadStart -> System.Threading.Thread
    new : System.Threading.ThreadStart * int -> System.Threading.Thread
    new : System.Threading.ParameterizedThreadStart -> System.Threading.Thread
    new : System.Threading.ParameterizedThreadStart * int -> System.Threading.Thread
    member Abort : unit -> unit
    member Abort : obj -> unit
    member ApartmentState : System.Threading.ApartmentState with get, set
    member CurrentCulture : System.Globalization.CultureInfo with get, set
    member CurrentUICulture : System.Globalization.CultureInfo with get, set
    member DisableComObjectEagerCleanup : unit -> unit
    member ExecutionContext : System.Threading.ExecutionContext
    member GetApartmentState : unit -> System.Threading.ApartmentState
    member GetCompressedStack : unit -> System.Threading.CompressedStack
    member GetHashCode : unit -> int
    member Interrupt : unit -> unit
    member IsAlive : bool
    member IsBackground : bool with get, set
    member IsThreadPoolThread : bool
    member Join : unit -> unit
    member Join : int -> bool
    member Join : System.TimeSpan -> bool
    member ManagedThreadId : int
    member Name : string with get, set
    member Priority : System.Threading.ThreadPriority with get, set
    member Resume : unit -> unit
    member SetApartmentState : System.Threading.ApartmentState -> unit
    member SetCompressedStack : System.Threading.CompressedStack -> unit
    member Start : unit -> unit
    member Start : obj -> unit
    member Suspend : unit -> unit
    member ThreadState : System.Threading.ThreadState
    member TrySetApartmentState : System.Threading.ApartmentState -> bool
    static member AllocateDataSlot : unit -> System.LocalDataStoreSlot
    static member AllocateNamedDataSlot : string -> System.LocalDataStoreSlot
    static member BeginCriticalRegion : unit -> unit
    static member BeginThreadAffinity : unit -> unit
    static member CurrentContext : System.Runtime.Remoting.Contexts.Context
    static member CurrentPrincipal : System.Security.Principal.IPrincipal with get, set
    static member CurrentThread : System.Threading.Thread
    static member EndCriticalRegion : unit -> unit
    static member EndThreadAffinity : unit -> unit
    static member FreeNamedDataSlot : string -> unit
    static member GetData : System.LocalDataStoreSlot -> obj
    static member GetDomain : unit -> System.AppDomain
    static member GetDomainID : unit -> int
    static member GetNamedDataSlot : string -> System.LocalDataStoreSlot
    static member MemoryBarrier : unit -> unit
    static member ResetAbort : unit -> unit
    static member SetData : System.LocalDataStoreSlot * obj -> unit
    static member Sleep : int -> unit
    static member Sleep : System.TimeSpan -> unit
    static member SpinWait : int -> unit
    static member VolatileRead : System.Byte -> System.Byte
    static member VolatileRead : int16 -> int16
    static member VolatileRead : int -> int
    static member VolatileRead : int64 -> int64
    static member VolatileRead : System.SByte -> System.SByte
    static member VolatileRead : uint16 -> uint16
    static member VolatileRead : uint32 -> uint32
    static member VolatileRead : System.IntPtr -> System.IntPtr
    static member VolatileRead : System.UIntPtr -> System.UIntPtr
    static member VolatileRead : uint64 -> uint64
    static member VolatileRead : float32 -> float32
    static member VolatileRead : float -> float
    static member VolatileRead : obj -> obj
    static member VolatileWrite : System.Byte * System.Byte -> unit
    static member VolatileWrite : int16 * int16 -> unit
    static member VolatileWrite : int * int -> unit
    static member VolatileWrite : int64 * int64 -> unit
    static member VolatileWrite : System.SByte * System.SByte -> unit
    static member VolatileWrite : uint16 * uint16 -> unit
    static member VolatileWrite : uint32 * uint32 -> unit
    static member VolatileWrite : System.IntPtr * System.IntPtr -> unit
    static member VolatileWrite : System.UIntPtr * System.UIntPtr -> unit
    static member VolatileWrite : uint64 * uint64 -> unit
    static member VolatileWrite : float32 * float32 -> unit
    static member VolatileWrite : float * float -> unit
    static member VolatileWrite : obj * obj -> unit
    static member Yield : unit -> bool
  end

Full name: System.Threading.Thread

  type: Thread
  implements: Runtime.InteropServices._Thread
  inherits: Runtime.ConstrainedExecution.CriticalFinalizerObject
Multiple overloads
Thread.Sleep(timeout: TimeSpan) : unit
Thread.Sleep(millisecondsTimeout: int) : unit
val displayChart : Color -> seq<float> -> Async<unit>

Full name: Demo.StandardAsync.displayChart

Create & display chart. The function needs to
 access user interface and should run on GUI thread.

val color : Color

  type: Color
  implements: IFormattable
  inherits: ValueType
val prices : seq<float>

  type: seq<float>
  inherits: Collections.IEnumerable
val chart : Canvas

  type: Canvas
  implements: MS.Internal.IManagedPeer
  implements: MS.Internal.IManagedPeerBase
  implements: MS.Internal.INativeCoreTypeWrapper
  implements: Automation.IAutomationElement
  inherits: Panel
  inherits: FrameworkElement
  inherits: UIElement
  inherits: DependencyObject
val createChart : float -> float -> Color -> seq<float> -> Canvas

Full name: Demo.Core.createChart
DependencyObject.SetValue(dp: DependencyProperty, value: obj) : unit
type Canvas =
  class
    inherit System.Windows.Controls.Panel
    new : unit -> System.Windows.Controls.Canvas
    static val LeftProperty : System.Windows.DependencyProperty
    static val TopProperty : System.Windows.DependencyProperty
    static val ZIndexProperty : System.Windows.DependencyProperty
    static member GetLeft : System.Windows.UIElement -> float
    static member GetTop : System.Windows.UIElement -> float
    static member GetZIndex : System.Windows.UIElement -> int
    static member SetLeft : System.Windows.UIElement * float -> unit
    static member SetTop : System.Windows.UIElement * float -> unit
    static member SetZIndex : System.Windows.UIElement * int -> unit
  end

Full name: System.Windows.Controls.Canvas

  type: Canvas
  implements: MS.Internal.IManagedPeer
  implements: MS.Internal.IManagedPeerBase
  implements: MS.Internal.INativeCoreTypeWrapper
  implements: Automation.IAutomationElement
  inherits: Panel
  inherits: FrameworkElement
  inherits: UIElement
  inherits: DependencyObject
field Canvas.LeftProperty
field Canvas.TopProperty
val holder : Canvas

Full name: Demo.Core.holder

  type: Canvas
  implements: MS.Internal.IManagedPeer
  implements: MS.Internal.IManagedPeerBase
  implements: MS.Internal.INativeCoreTypeWrapper
  implements: Automation.IAutomationElement
  inherits: Panel
  inherits: FrameworkElement
  inherits: UIElement
  inherits: DependencyObject
property Panel.Children: UIElementCollection
PresentationFrameworkCollection.Clear() : unit
PresentationFrameworkCollection.Add(value: UIElement) : unit
val main : Async<unit>

Full name: Demo.StandardAsync.main
val stockClicked : IObservable<Color * string>
val stock : Color * string
val btn : Button

  type: Button
  implements: MS.Internal.IManagedPeer
  implements: MS.Internal.IManagedPeerBase
  implements: MS.Internal.INativeCoreTypeWrapper
  implements: Automation.IAutomationElement
  inherits: Primitives.ButtonBase
  inherits: ContentControl
  inherits: Control
  inherits: FrameworkElement
  inherits: UIElement
  inherits: DependencyObject
module Seq

from Microsoft.FSharp.Collections
val zip : seq<'T1> -> seq<'T2> -> seq<'T1 * 'T2>

Full name: Microsoft.FSharp.Collections.Seq.zip
val stocks : (Color * string) list

Full name: Demo.Core.stocks

  type: (Color * string) list
  implements: Collections.IStructuralEquatable
  implements: IComparable<List<Color * string>>
  implements: IComparable
  implements: Collections.IStructuralComparable
  implements: Collections.Generic.IEnumerable<Color * string>
  implements: Collections.IEnumerable
val buttons : Button list

Full name: Demo.StandardAsync.buttons

  type: Button list
  implements: Collections.IStructuralEquatable
  implements: IComparable<List<Button>>
  implements: IComparable
  implements: Collections.IStructuralComparable
  implements: Collections.Generic.IEnumerable<Button>
  implements: Collections.IEnumerable
event Primitives.ButtonBase.Click: IEvent<RoutedEventHandler,RoutedEventArgs>
module Observable

from Microsoft.FSharp.Control
val map : ('T -> 'U) -> IObservable<'T> -> IObservable<'U>

Full name: Microsoft.FSharp.Control.Observable.map
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------

type List<'T> =
  | ( [] )
  | ( :: ) of 'T * 'T list
  with
    interface Collections.IEnumerable
    interface Collections.Generic.IEnumerable<'T>
    member Head : 'T
    member IsEmpty : bool
    member Item : index:int -> 'T with get
    member Length : int
    member Tail : 'T list
    static member Cons : head:'T * tail:'T list -> 'T list
    static member Empty : 'T list
  end

Full name: Microsoft.FSharp.Collections.List<_>

  type: List<'T>
  implements: Collections.IStructuralEquatable
  implements: IComparable<List<'T>>
  implements: IComparable
  implements: Collections.IStructuralComparable
  implements: Collections.Generic.IEnumerable<'T>
  implements: Collections.IEnumerable
val reduce : ('T -> 'T -> 'T) -> 'T list -> 'T

Full name: Microsoft.FSharp.Collections.List.reduce
val merge : IObservable<'T> -> IObservable<'T> -> IObservable<'T>

Full name: Microsoft.FSharp.Control.Observable.merge
val stock : string

  type: string
  implements: IComparable
  implements: ICloneable
  implements: IConvertible
  implements: IComparable<string>
  implements: seq<char>
  implements: Collections.IEnumerable
  implements: IEquatable<string>
Multiple items
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>

--------------------

type Async
with
  static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
  static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
  static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
  static member AwaitTask : task:Tasks.Task<'T> -> Async<'T>
  static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
  static member CancelDefaultToken : unit -> unit
  static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
  static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
  static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
  static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
  static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
  static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
  static member Ignore : computation:Async<'T> -> Async<unit>
  static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
  static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
  static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
  static member Sleep : millisecondsDueTime:int -> Async<unit>
  static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
  static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:Tasks.TaskCreationOptions * ?cancellationToken:CancellationToken -> Tasks.Task<'T>
  static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
  static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:Tasks.TaskCreationOptions -> Async<Tasks.Task<'T>>
  static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
  static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
  static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
  static member SwitchToNewThread : unit -> Async<unit>
  static member SwitchToThreadPool : unit -> Async<unit>
  static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
  static member CancellationToken : Async<CancellationToken>
  static member DefaultCancellationToken : CancellationToken
end

Full name: Microsoft.FSharp.Control.Async
static member Async.AwaitObservable : ev1:IObservable<'a> -> Async<'a>
val wc : WebClient
type WebClient =
  class
    new : unit -> System.Net.WebClient
    member AllowReadStreamBuffering : bool with get, set
    member AllowWriteStreamBuffering : bool with get, set
    member BaseAddress : string with get, set
    member CancelAsync : unit -> unit
    member Credentials : System.Net.ICredentials with get, set
    member DownloadStringAsync : System.Uri -> unit
    member DownloadStringAsync : System.Uri * obj -> unit
    member Encoding : System.Text.Encoding with get, set
    member Headers : System.Net.WebHeaderCollection with get, set
    member IsBusy : bool
    member OpenReadAsync : System.Uri -> unit
    member OpenReadAsync : System.Uri * obj -> unit
    member OpenWriteAsync : System.Uri -> unit
    member OpenWriteAsync : System.Uri * string -> unit
    member OpenWriteAsync : System.Uri * string * obj -> unit
    member ResponseHeaders : System.Net.WebHeaderCollection
    member UploadStringAsync : System.Uri * string -> unit
    member UploadStringAsync : System.Uri * string * string -> unit
    member UploadStringAsync : System.Uri * string * string * obj -> unit
    member UseDefaultCredentials : bool with get, set
  end

Full name: System.Net.WebClient
member WebClient.AsyncDownloadString : address:Uri -> Async<string>
type Uri =
  class
    new : string -> System.Uri
    new : string * bool -> System.Uri
    new : string * System.UriKind -> System.Uri
    new : System.Uri * string -> System.Uri
    new : System.Uri * string * bool -> System.Uri
    new : System.Uri * System.Uri -> System.Uri
    member AbsolutePath : string
    member AbsoluteUri : string
    member Authority : string
    member DnsSafeHost : string
    member Equals : obj -> bool
    member Fragment : string
    member GetComponents : System.UriComponents * System.UriFormat -> string
    member GetHashCode : unit -> int
    member GetLeftPart : System.UriPartial -> string
    member Host : string
    member HostNameType : System.UriHostNameType
    member IsAbsoluteUri : bool
    member IsBaseOf : System.Uri -> bool
    member IsDefaultPort : bool
    member IsFile : bool
    member IsLoopback : bool
    member IsUnc : bool
    member IsWellFormedOriginalString : unit -> bool
    member LocalPath : string
    member MakeRelative : System.Uri -> string
    member MakeRelativeUri : System.Uri -> System.Uri
    member OriginalString : string
    member PathAndQuery : string
    member Port : int
    member Query : string
    member Scheme : string
    member Segments : string []
    member ToString : unit -> string
    member UserEscaped : bool
    member UserInfo : string
    static val UriSchemeFile : string
    static val UriSchemeFtp : string
    static val UriSchemeGopher : string
    static val UriSchemeHttp : string
    static val UriSchemeHttps : string
    static val UriSchemeMailto : string
    static val UriSchemeNews : string
    static val UriSchemeNntp : string
    static val UriSchemeNetTcp : string
    static val UriSchemeNetPipe : string
    static val SchemeDelimiter : string
    static member CheckHostName : string -> System.UriHostNameType
    static member CheckSchemeName : string -> bool
    static member Compare : System.Uri * System.Uri * System.UriComponents * System.UriFormat * System.StringComparison -> int
    static member EscapeDataString : string -> string
    static member EscapeUriString : string -> string
    static member FromHex : char -> int
    static member HexEscape : char -> string
    static member HexUnescape : string * int -> char
    static member IsHexDigit : char -> bool
    static member IsHexEncoding : string * int -> bool
    static member IsWellFormedUriString : string * System.UriKind -> bool
    static member TryCreate : string * System.UriKind * System.Uri -> bool
    static member TryCreate : System.Uri * string * System.Uri -> bool
    static member TryCreate : System.Uri * System.Uri * System.Uri -> bool
    static member UnescapeDataString : string -> string
  end

Full name: System.Uri

  type: Uri
  implements: Runtime.Serialization.ISerializable
val root : string

Full name: Demo.Core.root

  type: string
  implements: IComparable
  implements: ICloneable
  implements: IConvertible
  implements: IComparable<string>
  implements: seq<char>
  implements: Collections.IEnumerable
  implements: IEquatable<string>
val prices : float []

  type: float []
  implements: ICloneable
  implements: Collections.IList
  implements: Collections.ICollection
  implements: Collections.IStructuralComparable
  implements: Collections.IStructuralEquatable
  implements: Collections.Generic.IList<float>
  implements: Collections.Generic.ICollection<float>
  implements: seq<float>
  implements: Collections.IEnumerable
  inherits: Array
static member Async.StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
val main : Async<unit>

Full name: Demo.StandardAsync.StartChild.main
let stockClicked =
  [ for stock, btn in Seq.zip stocks buttons ->
      btn.Click |> Observable.map (fun _ -> stock) ]
  |> List.reduce Observable.merge
val token : Async<float []>
static member Async.StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
val main : Async<unit>

Full name: Demo.StandardAsync.Switching.main
val ctx : SynchronizationContext
type SynchronizationContext =
  class
    new : unit -> System.Threading.SynchronizationContext
    member CreateCopy : unit -> System.Threading.SynchronizationContext
    member IsWaitNotificationRequired : unit -> bool
    member OperationCompleted : unit -> unit
    member OperationStarted : unit -> unit
    member Post : System.Threading.SendOrPostCallback * obj -> unit
    member Send : System.Threading.SendOrPostCallback * obj -> unit
    member Wait : System.IntPtr [] * bool * int -> int
    static member Current : System.Threading.SynchronizationContext
    static member SetSynchronizationContext : System.Threading.SynchronizationContext -> unit
  end

Full name: System.Threading.SynchronizationContext
property SynchronizationContext.Current: SynchronizationContext
static member Async.SwitchToThreadPool : unit -> Async<unit>
static member Async.SwitchToContext : syncContext:SynchronizationContext -> Async<unit>

Published: Friday, 10 June 2011, 11:36 PM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: research, asynchronous, f#, functional