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.SwitchToThreadPool
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
- Browse the source code on GitHub
- Download the source code (ZIP)
Full name: Demo.StandardAsync.extractStockPrices
Parse downloaded data set. This is a CPU-expensive
computation that should not block the user-interface.
type: string
implements: IComparable
implements: ICloneable
implements: IConvertible
implements: IComparable<string>
implements: seq<char>
implements: Collections.IEnumerable
implements: IEquatable<string>
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>
type: int
implements: IComparable
implements: IFormattable
implements: IConvertible
implements: IComparable<int>
implements: IEquatable<int>
inherits: ValueType
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
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
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
let infos = line.Split(',')
yield float infos.[1] |]
|> Seq.take count |> Array.ofSeq |> Array.rev
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
Thread.Sleep(timeout: TimeSpan) : unit
Thread.Sleep(millisecondsTimeout: int) : unit
Full name: Demo.StandardAsync.displayChart
Create & display chart. The function needs to
access user interface and should run on GUI thread.
type: Color
implements: IFormattable
inherits: ValueType
type: seq<float>
inherits: Collections.IEnumerable
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
Full name: Demo.Core.createChart
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
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
Full name: Demo.StandardAsync.main
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
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.zip
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
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
from Microsoft.FSharp.Control
Full name: Microsoft.FSharp.Control.Observable.map
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
Full name: Microsoft.FSharp.Collections.List.reduce
Full name: Microsoft.FSharp.Control.Observable.merge
type: string
implements: IComparable
implements: ICloneable
implements: IConvertible
implements: IComparable<string>
implements: seq<char>
implements: Collections.IEnumerable
implements: IEquatable<string>
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
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
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
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>
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
Full name: Demo.StandardAsync.StartChild.main
[ for stock, btn in Seq.zip stocks buttons ->
btn.Click |> Observable.map (fun _ -> stock) ]
|> List.reduce Observable.merge
Full name: Demo.StandardAsync.Switching.main
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
Published: Friday, 10 June 2011, 11:36 PM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: research, asynchronous, f#, functional