TP

Safer asynchronous workflows for GUI programming

In the previous article, I discussed how to use F# asynchronous work­flows for creating reactive user-interfaces. One of the main concerns was to avoid blocking the GUI thread (to prevent the user-interface from freezing). The workflow shouldn't perform any CPU-intensive compu­tation when running on the GUI thread.

The standard F# library provides two ways to run a computation on a background thread from an asynchronous workflow. The StartChild operation starts an operation in the thread pool and returns a workflow that can be called using asynchronous (non-blocking) let! construct. The SwitchToThreadPool operation can be called using do! and resumes the rest of the workflow on a background thread.

When using the SwitchToThreadPool operation, we also need to eventually use SwitchToContext to transfer the execution back to the GUI thread (after completing the CPU-intensive calculations). In this article, I describe a variation of F# asynchronous workflows that keeps track of the running thread in the type of the computation. As a result, calling a workflow that should be executed on a GUI thread from a background thread is a compile-time error as opposed to failing at runtime.

Introducing checked async

The easiest way to implement checked asynchronous workflows is to just wrap the standard F# implementation and add type parameters for keeping the additional information. Then we also need to create a wrapper for a computation builder that will correctly propagate the additional information. Let's first look at the type declarations and then explore some examples. The definition of computation builder is briefly discussed at the end of the article.

1: /// Represents an asynchronous computation that starts
2: /// on the 'Pre thread, ends on the 'Post thread and
3: /// produces a value of type 'Value.
4: type CheckedAsync<'Pre, 'Post, 'Value> = (...)
5: 
6: // Two phantom types to represent threads
7: type GuiThread = (...)
8: type BackgroundThread = (...)

The last type parameter of CheckedAsync is the type of the value produced by the asynchronous workflow and is uses as an argument to the wrapped Async. The two additional parameters are called phantom type parameters, because they do not appear anywhere in the definition. In many ways, they are similar to units of measure - they just add additional information to the type that is checked at compile-time.

The next two types can be used in place of the first two type parameters. For example, a computation of type CheckedAsync<GuiThread, BackgroundThread, string> represents an asynchronous computation that starts on the GUI thread (where it can access user-interface elements), then switches to the background thread (which can be done using SwitchToThreadPool), then performs some CPU-intensive computation and produces a string value. When called using let! from another asynchronous workflow, it resumes the rest of the workflow on the background thread (which is quite subtle behavior - most of the standard asynchronous operations resume on the same thread where they started).

Processing data and creating charts

To demonstrate how checked asynchronous workflows work in practice, we'll reimplement the examples from my previous article. The sample application downloads stock prices, processes them (which may be CPU consuming) and then creates a chart (which must be run on the GUI thread). The following two helper functions will be called from the main asynchronous workflow:

 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 = asyncOn background {
 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 = asyncOn gui {
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 functions are declared almost like ordinary asynchronous workflow. The only difference is that the snippet uses a parameterized computation builder asyncOn. The argument specifies the thread where the computation should start (the values gui and background are just dummy values to specify the type using a nice syntax). The F# compiler infers the thread where the computation will complete from the initial thread and from the code of the workflow. Both of the functions are quite simple and they do not change the thread where the workflow is running. As a result the type of the first workflow is CheckedAsync<BackgroundThread, BackgroundThread, float[]> and the type of the second one is CheckedAsync<GuiThread, GuiThread, unit>.

Type-checking threads

The main workflow needs to call the above two functions. When it gets the data, it processes them to extract the prices (and possibly other statistics) and then draws the chart. Let's now look how could we go about writing a composed workflow. In this case, we don't need to be explicit about the thread where the computation starts. To leave the compiler to infer this, we can use asyncChecked computation builder instead of more specific asyncOn. In the first attempt we just try to call the two functions:

1: let wrongWorkflow data = asyncChecked {
2:   // Starts executing on background thread...
3:   let! prices = extractStockPrices data 500
4:   // ...but cannot continue on the GUI thread!
5:   do! displayChart Color.Red prices }

As the snippet shows, the code doesn't compile. The problem is that the workflow created by extractStockPrices ends on the background thread, but the workflow created by displayChart can be only started on the GUI thread. The definition of Bind in the computation builder (discussed below) specifies that the end-thread of the first operation must match the begin-thread of the second operation, which is not the case in this snippet. Also note that the F# type inference correctly deduced that the entire wrongWorkflow starts on the background thread, because this is where the first operation starts. This also follows from the definition of the computation builder.

To fix the error, we need to run the workflow created by displayChart on the GUI thread. One way to do this is to use a special operation named SwitchToGui that changes the current thread to the GUI thread using the Dispatcher type provided by Silverlight. The method has the following type:

1: type CheckedAsync with
2:   /// Continue executing on the GUI thread (regardless of the current thread)
3:   static member SwitchToGui : unit -> CheckedAsync<'Pre, GuiThread, unit>

The type of the workflow specifies that it can be started on any thread. The type parameter 'Pre can be specified to both GuiThread (which would be legal, but not very useful) and to BackgroundThread. The resulting thread is however always GuiThread. Finally, the last type parameter specifies that the workflow returns unit, which means that it can be called using the do! syntax:

1: let goodWorkflow data = asyncChecked {
2:   // Start executing on background thread
3:   let! prices = extractStockPrices data 500
4:   // Switch to the GUI thread
5:   do! CheckedAsync.SwitchToGui()
6:   // And display chart on the GUI thread
7:   do! displayChart Color.Red prices }

With the additional thread transition in the middle, the snippet now type-checks and it creates a workflow that starts on the background thread, does some work, switches to the GUI thread and then displays the chart and returns. You can verify that by looking at the (inferred) type of the goodWorkflow function.

Verifying stock price sample

Let's now look how checked asynchronous workflows work on a larger example. First of all, the previous article included an incorrect example that called the two functions declared above from a single asynchronous workflow without any thread switching. The example compiled, but it caused the user-interface to hang, which is what we want to avoid. The main workflow is expected to start on the GUI thread. If we replace ordinary async with asyncOn gui and use operations provided by CheckedAsync (instead of Async), the snippet no longer compiles:

 1: let main = asyncOn gui {
 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 = CheckedAsync.AwaitObservable(stockClicked)
 8:     // Download data for stock (non-blocking operation)
 9:     let wc = new WebClient()
10:     let! data = wc.AsyncDownloadString(Uri(root + stock))    
11:     // Process data (CPU-bound operation)
12:     let! prices = extractStockPrices data 500
13:     // Create & display the chart (GUI operation)
14:     do! displayChart color prices }
15: 
16: // Start the workflow immediately (on GUI thread)
17: do main |> CheckedAsync.StartImmediate

The error is the same as in the earlier brief example - we cannot call CPU-bound workflow extractStockPrices from a workflow that runs on the GUI thread. When writing code using checked workflows, we need to use checked versions of all standard operations. These are implemented as simple wrappers, but they annotate the type with the specification of threading behavior (which is otherwise difficult to find, even in the documentation). The operations used in the above example have the following signatures:

 1: type CheckedAsync with
 2:   /// Waiting for event occurrence is allowed on GUI thread only
 3:   static member AwaitObservable  : IObservable<'T> -> 
 4:                                    CheckedAsync<GuiThread, GuiThread, 'T>
 5:   /// Starts a computation on the (current) GUI thread
 6:   static member StartImmediate   : CheckedAsync<GuiThread, 'Post, unit> 
 7: 
 8: type System.Net.WebClient with
 9:   /// Downloads string and returns to original thread
10:   member AsyncDownloadString : Uri -> CheckedAsync<'T, 'T, string>

The types of operations now also carry the specification of threading behavior.

Now that we've seen numerous primitive asynchronous operations, let's look how to fix the above example. The following two sections implement the two approaches introduced in the previous article. We start with switching of the current thread and then look at starting computation as a child.

Switching between threads

After introducing the type-checking of threading behavior, the switching approach makes more sense. In the earlier snippet, I demonstrated how the SwitchToGui operation makes that possible, but that's a Silverlight-specific operation. In general, F# asynchronous workflows use standard .NET SynchronizationContext type that represents an arbitrary thread (it can be either background or GUI). Current context can be obtained using Synchronization­Context.­Current. The following snippet uses checked wrappers for these operations:

 1: let main = asyncOn gui {
 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 = CheckedAsync.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 = CheckedAsync.CurrentContext()
14:     do! CheckedAsync.SwitchToThreadPool()
15:     let! prices = extractStockPrices data 500
16:     do! CheckedAsync.SwitchToContext(ctx)
17: 
18:     // Create & display the chart (GUI operation)
19:     do! displayChart color prices }

Before we can call the extractStockPrices function, we need to switch the execution of the workflow to a back­ground thread. This is done simply using SwitchToThreadPool which resumes the workflow on a thread from the thread pool. To switch back, we use SwitchToContext, which resumes the workflow on a thread represented by the SynchronizationContext used as an argument. This may be either a GUI or a background thread - to make this type-safe, we need to keep track of the thread in the type of synchronization context too, so we use a wrapper Synchronization­Context<'Thread>. The type parameter 'Thread represents the thread captured by the context.

When getting the current context, we use an operation CurrentContextprovided by CheckedAsync. It returns a synchronization context of the current thread and annotates it with an appropriate type. When executed from a workflow running on a thread SomeThread, the result will have a type Synchronization­Context<SomeThread>. The type signatures of the operation used in the above snippet clarify the behavior:

 1: type CheckedAsync with
 2:   /// Get current context (the thread is also kept in the type)
 3:   static member CurrentContext :
 4:     unit -> CheckedAsync<'T, 'T, SynchronizationContext<'T>>
 5:   /// Switch to new background thread (from any thread)
 6:   static member SwitchToThreadPool :
 7:     unit -> CheckedAsync<'T, BackgroundThread, unit>
 8:   /// Switch to thread represented by a synchronization context
 9:   static member SwitchToContext : 
10:     SynchronizationContext<'T> -> CheckedAsync<'Pre, 'T, unit>

The CurrentContext method creates a workflow that starts on a thread 'T, ends on a thread 'T and returns a synchronization context representing the same thread 'T. When used in the snippet above, the operation is called from the GUI thread and so the type of ctx is Synchronization­Context<GuiThread>. The SwitchToContext operation takes a typed synchronization context representing a thread 'T and creates an asynchronous workflow that resumes on a thread specified by the context. In the above example, the operation switches the execution to a GUI thread (stored previously), so that we can call the displayChart function.

The use of generic types when working with synchronization contexts demonstrated one of the fascinating things about programming in F# that is carried over to checking of threads in asynchronous workflows. We can easily write polymorphic code that works with any values and executes on any threads while still checking that computations are composed correctly. With a bit of effort, it is even possible to capture dynamic behavior introduced by synchro­nization contexts.

Starting child workflow

Another way to run a computation without blocking the GUI thread is to start it as a child workflow. This is done using the StartChild operation that guarantees that it will not introduce blocking. When using this approach, it is almost impossible to write the code in a wrong way, so there is less need for thread checking. However, when we annotate the operation, it has an interesting type that is worth demonstrating:

 1: let main = asyncOn gui {
 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 = CheckedAsync.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 = CheckedAsync.StartChild(extractStockPrices data 500)
14:     let! prices = token
15: 
16:     // Create & display the chart (GUI operation)
17:     do! displayChart color prices }

The StartChild operation creates a workflow that can be executed on any thread (and finishes on the same thread). We immediately execute it using the let! construct. This starts the workflow given as an argument on the background thread (which is also specified by the type of the argument). The result is a token that can be used for waiting for the completion of the background operation. This operation can be (again) used on any thread, because it performs non-blocking waiting. The whole type signature looks as follows:

1: type CheckedAsync with
2:   /// Start child on a background thread (without changing current thread)
3:   static member StartChild : 
4:     CheckedAsync<BackgroundThread, 'Post, 'R> ->
5:     CheckedAsync<'Pre1, 'Pre1, CheckedAsync<'Pre2, 'Pre2, 'R>>

As already explained, the argument should be a workflow that can be started on the background thread and we don't care where it completes. The result of the operation is a checked asynchronous workflow (that waits for the completion) wrapped in another checked asynchronous operation (that starts the task). These two operations use two different type parameters to track the thread, because it is perfectly possible to use them from different threads. For example, we may start the background operation on a GUI thread and then pass the result to some other workflow that will wait on another background thread (in which case 'Pre1 would be GuiThread and 'Pre2 would be BackgroundThread).

Looking under the cover

The key idea of the approach demonstrated in this article is to extend the type of asynchronous workflows to also include two type parameters that do not appear in the type definition (these are called phantom types). The additional type parameters specify threads where the workflow starts and where it completes. Then we need to annotate standard async operations with the additional types to specify the behavior. I demonstrated type signatures of several of the operations of the CheckedAsync type earlier in the article. The implementation can be found in the source code, but is mostly trivial - just wrap the operation from standard F# async and add types.

The last important piece is to specify how the operations compose. When you run one operation and then some other operation, the resulting thread of the first one must match the starting thread of the second one. This rule is also used by the F# compiler to infer the types of operations written using asyncChecked and to infer the type of operations that can be run on any thread and preserve the thread (such as AsyncDownloadString).

The composition rules are defined by the F# computation builder. The following snippet shows the type signatures of the standard operations and also adds While (which was used in the example and is quite interesting too):

 1: type CheckedAsyncBuilder =
 2:   /// Return creates async workflow that resumes on the same thread
 3:   member Return : 'V -> CheckedAsync<'Thread, 'Thread, 'V>
 4:   /// Composed computation starts on 'Pre, switches to 'Interm and
 5:   /// then switches to 'Post (The result hides the intermediate step)
 6:   member Bind   : CheckedAsync<'Pre, 'Interm, 'V1> -> 
 7:                   ('V1 -> CheckedAsync<'Interm, 'Post, 'V2>) ->
 8:                   CheckedAsync<'Pre, 'Post, 'V2>
 9:   /// The body of a while loop must end at the same thread as where it 
10:   /// was started (otherwise it cannot be composed to run multiple times)
11:   member While  : (unit -> bool) -> CheckedAsync<'Thread, 'Thread, unit> ->
12:                   CheckedAsync<'Thread, 'Thread, unit>

If we consider just the last type parameter (representing the value returned by a checked asynchronous workflow), the types are the standard types of a monad (and an F# computation builder). However, the first two type parameters add an interesting additional power:

Another interesting operation that is not discussed in detail in this article is TryWith, which represents an exception handler. This is quite tricky, because the exception will be handled on the thread where it happened, but that can be any thread where the body of the workflow can run!

In the source code, I solved this problem by adding a new type named AnyThread, which is not compatible with any other type. For practical purposes, this is probably sufficient, but you can imagine several possible extensions. The AnyThread type could be a base type of both GuiThread and BackgroundThread making it possible to compose computations that end on a specific thread with a computation that starts on any (unknown) thread. We could also use even more complicated types in to track all possible threads that can be used by the body and then have types like AnyOf<GuiThread, BackgroundThread> representing any of the two. However, this doesn't add much practical value and it may be only interesting from a research perspective.

Some of the ideas that I mentioned above have been actually done in different contexts. Although this is a blog post and not a research paper, the next section mentions several of the related academic papers (as well as some readable blog posts). If you're interested only in practical uses of checked asynchronous workflows, then you can safely skip it.

Related work

The definition of a monad used in this article adds two type parameters to capture the required initial state (a pre-condition) and a resulting state (a post-condition). This idea is quite common when reasoning about programming languages and it can be traced back to Hoare logic [1]. In Hoare logic, the pre- and post-conditions are not written as part of the progam - they are just a way of reasoning about existing programs. More recent work on "Hoare type theory" tries to embed this reasoning in the types of programs.

More specifically, the signature of monadic operations that I used in the previous section is also called parameterized monad and has been studied from the theoretical perspective [2], but there is also a more readable introduction [3]. It can be used for a variety of other things (besides threads in asynchronous workflows), such as tracking of acative file handles [4].

Summary

In this article, I demonstrated an extension of F# asynchronous workflows that makes them safer. It adds a way to track where the workflow is executing - whether it is a GUI thread or a background thread. We can create computations that should be executed on the background thread using asyncOn background. These can safely perform long-running CPU-intensive computations and we know that this won't freeze the user-interface. Similarly, we can create workflows that can be only executed on the GUI thread using asyncOn gui. These computations can safely access user-interface elements.

Thanks to the right definition of a computation builder, the current thread is automatically tracked in the asynchronous workflow syntax. Calling a workflow that should run on a particular thread from a wrong thread is a compile-time error. This adds very useful additional checking. The examples in this article relied on some help from the programmer. We had to explicitly mark extractStockPrices as background-thread computaiton and displayChart as GUI-thread computation. In a perfect world, the compiler would know that certain operations should be executed only on certain threads and it would infer the type for us...

Downloads & Source code

References 

Multiple items
type CheckedAsync<'Pre,'Post,'Value> = | SA of Async<'Value>

Full name: Demo.CheckedAsync.CheckedAsync<_,_,_>

Represents asynchronous computation that starts
 on the 'Pre thread, ends on the 'Post thread and
 produces a value of type 'Value.


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

type CheckedAsync =
  class
    static member CurrentContext : unit -> CheckedAsync<'T,'T,SynchronizationContext<'T>>
    static member Start : CheckedAsync<BackgroundThread,'Post,unit> -> unit
    static member StartChild : CheckedAsync<BackgroundThread,'Post,'R> -> CheckedAsync<'Pre1,'Pre1,CheckedAsync<'Pre2,'Pre2,'R>>
    static member StartImmediate : CheckedAsync<GuiThread,'Post,unit> -> unit
    static member SwitchToContext : ctx:SynchronizationContext<'C> -> CheckedAsync<'Pre,'C,unit>
    static member SwitchToGui : unit -> CheckedAsync<'Pre,GuiThread,unit>
    static member SwitchToThreadPool : unit -> CheckedAsync<'Pre,BackgroundThread,unit>
    static member UnsafeSwitchToAnything : unit -> CheckedAsync<'a0,'a1,unit>
  end

Full name: FSharp.CheckedAsync.CheckedAsync
SA of Async<'Value>
Multiple items
type GuiThread =
  interface
  end

Full name: Demo.CheckedAsync.GuiThread

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

GuiThread
interface end
Multiple items
type BackgroundThread =
  interface
  end

Full name: Demo.CheckedAsync.BackgroundThread

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

BackgroundThread
val extractStockPrices : string -> int -> CheckedAsync<BackgroundThread,BackgroundThread,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 asyncOn : 'T -> CheckedAsyncBuilderForThread<'T>

Full name: FSharp.CheckedAsync.Values.asyncOn
val background : BackgroundThread

Full name: FSharp.CheckedAsync.Values.background
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> -> CheckedAsync<GuiThread,GuiThread,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 gui : GuiThread

Full name: FSharp.CheckedAsync.Values.gui
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 wrongWorkflow : string -> CheckedAsync<BackgroundThread,'a,unit>

Full name: Demo.StandardAsync.wrongWorkflow
val asyncChecked<'T> : CheckedAsyncBuilderForThread<'T>

Full name: FSharp.CheckedAsync.Values.asyncChecked
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
Type mismatch. Expecting a
    CheckedAsync<BackgroundThread,'a,'b>
but given a
    CheckedAsync<GuiThread,GuiThread,unit>
The type 'BackgroundThread' does not match the type 'GuiThread'
val goodWorkflow : string -> CheckedAsync<BackgroundThread,GuiThread,unit>

Full name: Demo.StandardAsync.goodWorkflow
Multiple items
module CheckedAsync

from Demo

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

type CheckedAsync<'Pre,'Post,'Value> = | SA of Async<'Value>

Full name: FSharp.CheckedAsync.CheckedAsync<_,_,_>

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

type CheckedAsync =
  class
    static member CurrentContext : unit -> CheckedAsync<'T,'T,SynchronizationContext<'T>>
    static member Start : CheckedAsync<BackgroundThread,'Post,unit> -> unit
    static member StartChild : CheckedAsync<BackgroundThread,'Post,'R> -> CheckedAsync<'Pre1,'Pre1,CheckedAsync<'Pre2,'Pre2,'R>>
    static member StartImmediate : CheckedAsync<GuiThread,'Post,unit> -> unit
    static member SwitchToContext : ctx:SynchronizationContext<'C> -> CheckedAsync<'Pre,'C,unit>
    static member SwitchToGui : unit -> CheckedAsync<'Pre,GuiThread,unit>
    static member SwitchToThreadPool : unit -> CheckedAsync<'Pre,BackgroundThread,unit>
    static member UnsafeSwitchToAnything : unit -> CheckedAsync<'a0,'a1,unit>
  end

Full name: FSharp.CheckedAsync.CheckedAsync
static member CheckedAsync.SwitchToGui : unit -> CheckedAsync<'Pre,GuiThread,unit>
type Color =
  struct
    member A : System.Byte with get, set
    member B : System.Byte with get, set
    member Equals : obj -> bool
    member Equals : System.Windows.Media.Color -> bool
    member G : System.Byte with get, set
    member GetHashCode : unit -> int
    member R : System.Byte with get, set
    member ToString : unit -> string
    member ToString : System.IFormatProvider -> string
    static member FromArgb : System.Byte * System.Byte * System.Byte * System.Byte -> System.Windows.Media.Color
  end

Full name: System.Windows.Media.Color

  type: Color
  implements: IFormattable
  inherits: ValueType
property Color.Red: Color
val main : CheckedAsync<GuiThread,GuiThread,unit>

Full name: Demo.StandardAsync.main
let stockClicked =
  [ for stock, btn in Seq.zip stocks buttons ->
      btn.Click |> Observable.map (fun _ -> stock) ]
  |> List.reduce 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>
static member CheckedAsync.AwaitObservable : ev1:IObservable<'a> -> CheckedAsync<GuiThread,GuiThread,'a>
val stockClicked : IObservable<Color * string>
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 -> CheckedAsync<'T,'T,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 : 'a (requires 'a :> seq<float>)

  type: 'a
  implements: seq<float>
  implements: Collections.IEnumerable
Type mismatch. Expecting a
    CheckedAsync<GuiThread,'a,'b>
but given a
    CheckedAsync<BackgroundThread,BackgroundThread,float []>
The type 'GuiThread' does not match the type 'BackgroundThread'
static member CheckedAsync.StartImmediate : CheckedAsync<GuiThread,'Post,unit> -> unit
val main : CheckedAsync<GuiThread,GuiThread,unit>

Full name: Demo.StandardAsync.Switching.main
val ctx : SynchronizationContext<GuiThread>
static member CheckedAsync.CurrentContext : unit -> CheckedAsync<'T,'T,SynchronizationContext<'T>>
static member CheckedAsync.SwitchToThreadPool : unit -> CheckedAsync<'Pre,BackgroundThread,unit>
static member CheckedAsync.SwitchToContext : ctx:SynchronizationContext<'C> -> CheckedAsync<'Pre,'C,unit>
val main : CheckedAsync<GuiThread,GuiThread,unit>

Full name: Demo.StandardAsync.StartChild.main
val token : CheckedAsync<GuiThread,GuiThread,float []>
static member CheckedAsync.StartChild : CheckedAsync<BackgroundThread,'Post,'R> -> CheckedAsync<'Pre1,'Pre1,CheckedAsync<'Pre2,'Pre2,'R>>
Multiple items
type CheckedAsync<'Pre,'Post,'Value> = | SA of Async<'Value>

Full name: FSharp.CheckedAsync.CheckedAsync<_,_,_>

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

type CheckedAsync

Full name: FSharp.CheckedAsync.CheckedAsync
static member CheckedAsync.SwitchToGui : unit -> CheckedAsync<'Pre,GuiThread,unit>

Full name: FSharp.CheckedAsync.Extensions.SwitchToGui

Continue executing on the GUI thread (regardless of the current thread)
type unit = Unit

Full name: Microsoft.FSharp.Core.unit

  type: unit
  implements: IComparable
Multiple items
type GuiThread =
  interface
  end

Full name: FSharp.CheckedAsync.GuiThread

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

GuiThread
static member CheckedAsync.AwaitObservable : IObservable<'T> -> CheckedAsync<GuiThread,GuiThread,'T>

Full name: FSharp.CheckedAsync.Extensions.AwaitObservable

Waiting for event occurrence is allowed on GUI thread only
Multiple items
type IObservable<'T> =
  interface
    member Subscribe : System.IObserver<'T> -> System.IDisposable
  end

Full name: System.IObservable<_>

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

IObservable
static member CheckedAsync.StartImmediate : CheckedAsync<GuiThread,'Post,'R>

Full name: FSharp.CheckedAsync.Extensions.StartImmediate

Starts a computation on the (current) GUI thread
namespace System
namespace System.Net
type WebClient =
  class
    inherit System.ComponentModel.Component
    new : unit -> System.Net.WebClient
    member BaseAddress : string with get, set
    member CachePolicy : System.Net.Cache.RequestCachePolicy with get, set
    member CancelAsync : unit -> unit
    member Credentials : System.Net.ICredentials with get, set
    member DownloadData : string -> System.Byte []
    member DownloadData : System.Uri -> System.Byte []
    member DownloadDataAsync : System.Uri -> unit
    member DownloadDataAsync : System.Uri * obj -> unit
    member DownloadFile : string * string -> unit
    member DownloadFile : System.Uri * string -> unit
    member DownloadFileAsync : System.Uri * string -> unit
    member DownloadFileAsync : System.Uri * string * obj -> unit
    member DownloadString : string -> string
    member DownloadString : System.Uri -> string
    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 OpenRead : string -> System.IO.Stream
    member OpenRead : System.Uri -> System.IO.Stream
    member OpenReadAsync : System.Uri -> unit
    member OpenReadAsync : System.Uri * obj -> unit
    member OpenWrite : string -> System.IO.Stream
    member OpenWrite : System.Uri -> System.IO.Stream
    member OpenWrite : string * string -> System.IO.Stream
    member OpenWrite : System.Uri * string -> System.IO.Stream
    member OpenWriteAsync : System.Uri -> unit
    member OpenWriteAsync : System.Uri * string -> unit
    member OpenWriteAsync : System.Uri * string * obj -> unit
    member Proxy : System.Net.IWebProxy with get, set
    member QueryString : System.Collections.Specialized.NameValueCollection with get, set
    member ResponseHeaders : System.Net.WebHeaderCollection
    member UploadData : string * System.Byte [] -> System.Byte []
    member UploadData : System.Uri * System.Byte [] -> System.Byte []
    member UploadData : string * string * System.Byte [] -> System.Byte []
    member UploadData : System.Uri * string * System.Byte [] -> System.Byte []
    member UploadDataAsync : System.Uri * System.Byte [] -> unit
    member UploadDataAsync : System.Uri * string * System.Byte [] -> unit
    member UploadDataAsync : System.Uri * string * System.Byte [] * obj -> unit
    member UploadFile : string * string -> System.Byte []
    member UploadFile : System.Uri * string -> System.Byte []
    member UploadFile : string * string * string -> System.Byte []
    member UploadFile : System.Uri * string * string -> System.Byte []
    member UploadFileAsync : System.Uri * string -> unit
    member UploadFileAsync : System.Uri * string * string -> unit
    member UploadFileAsync : System.Uri * string * string * obj -> unit
    member UploadString : string * string -> string
    member UploadString : System.Uri * string -> string
    member UploadString : string * string * string -> string
    member UploadString : System.Uri * string * string -> string
    member UploadStringAsync : System.Uri * string -> unit
    member UploadStringAsync : System.Uri * string * string -> unit
    member UploadStringAsync : System.Uri * string * string * obj -> unit
    member UploadValues : string * System.Collections.Specialized.NameValueCollection -> System.Byte []
    member UploadValues : System.Uri * System.Collections.Specialized.NameValueCollection -> System.Byte []
    member UploadValues : string * string * System.Collections.Specialized.NameValueCollection -> System.Byte []
    member UploadValues : System.Uri * string * System.Collections.Specialized.NameValueCollection -> System.Byte []
    member UploadValuesAsync : System.Uri * System.Collections.Specialized.NameValueCollection -> unit
    member UploadValuesAsync : System.Uri * string * System.Collections.Specialized.NameValueCollection -> unit
    member UploadValuesAsync : System.Uri * string * System.Collections.Specialized.NameValueCollection * obj -> unit
    member UseDefaultCredentials : bool with get, set
  end

Full name: System.Net.WebClient

  type: Net.WebClient
  implements: ComponentModel.IComponent
  implements: IDisposable
  inherits: ComponentModel.Component
  inherits: MarshalByRefObject
member Net.WebClient.AsyncDownloadString : Uri -> CheckedAsync<'T,'T,string>

Full name: FSharp.CheckedAsync.Extensions.AsyncDownloadString

Downloads string and returns to original thread
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
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>
static member CheckedAsync.CurrentContext : unit -> CheckedAsync<'T,'T,SynchronizationContext<'T>>

Full name: FSharp.CheckedAsync.Extensions.CurrentContext

Get current context (the thread is also kept in the type)
type SynchronizationContext<'T>

Full name: FSharp.CheckedAsync.SynchronizationContext<_>
static member CheckedAsync.SwitchToThreadPool : unit -> CheckedAsync<'T,BackgroundThread,unit>

Full name: FSharp.CheckedAsync.Extensions.SwitchToThreadPool

Switch to new background thread (from any thread)
Multiple items
type BackgroundThread =
  interface
  end

Full name: FSharp.CheckedAsync.BackgroundThread

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

BackgroundThread
static member CheckedAsync.SwitchToContext : SynchronizationContext<'T> -> CheckedAsync<'Pre,'T,unit>

Full name: FSharp.CheckedAsync.Extensions.SwitchToContext

Switch to thread represented by a synchronization context
static member CheckedAsync.StartChild : CheckedAsync<BackgroundThread,'Post,'R> -> CheckedAsync<'Pre1,'Pre1,CheckedAsync<'Pre2,'Pre2,'R>>

Full name: FSharp.CheckedAsync.Extensions.StartChild

Start child on a background thread (without changing current thread)
type CheckedAsyncBuilder =
  class
    member Bind : CheckedAsync<'Pre,'Interm,'V1> -> ('V1 -> CheckedAsync<'Interm,'Post,'V2>) -> CheckedAsync<'Pre,'Post,'V2>
    member Return : 'V -> CheckedAsync<'Thread,'Thread,'V>
    member While : (unit -> bool) -> CheckedAsync<'Thread,'Thread,unit> -> CheckedAsync<'Thread,'Thread,unit>
  end

Full name: FSharp.CheckedAsync.CheckedAsyncBuilder
member CheckedAsyncBuilder.Return : 'V -> CheckedAsync<'Thread,'Thread,'V>

Full name: FSharp.CheckedAsync.CheckedAsyncBuilder.Return

Return creates async workflow that resumes on the same thread
member CheckedAsyncBuilder.Bind : CheckedAsync<'Pre,'Interm,'V1> -> ('V1 -> CheckedAsync<'Interm,'Post,'V2>) -> CheckedAsync<'Pre,'Post,'V2>

Full name: FSharp.CheckedAsync.CheckedAsyncBuilder.Bind

Composed computation starts on 'Pre, switches to 'Interm and
 then switches to 'Post (The result hides the intermediate step)

member CheckedAsyncBuilder.While : (unit -> bool) -> CheckedAsync<'Thread,'Thread,unit> -> CheckedAsync<'Thread,'Thread,unit>

Full name: FSharp.CheckedAsync.CheckedAsyncBuilder.While

The body of a while loop must end at the same thread as where it
 was started (otherwise it cannot be composed to run multiple times)

type bool = Boolean

Full name: Microsoft.FSharp.Core.bool

  type: bool
  implements: IComparable
  implements: IConvertible
  implements: IComparable<bool>
  implements: IEquatable<bool>
  inherits: ValueType

Published: Wednesday, 15 June 2011, 9:36 PM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: research, f#, asynchronous, functional