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([| '

' |], 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>

