Safer asynchronous workflows for GUI programming
In the previous article, I discussed how to use F# asynchronous workflows 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 computation 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.
- The
AwaitObservable
operation can be used only on the GUI thread and it resumes the workflow on the GUI thread. This works for standard F# events, but other sources ofIObservable<'T>
may behave differently (the right solution would be to use a more expressive typeIObservable<'Thread, 'T>
with'Thread
describing where events are triggered). - The
StartImmediate
operation starts a workflow on the current thread. It is expected to be used on the main thread (outside of asynchronous workflows), so the type specifies that the argument will be executed on the GUI thread. The operation doesn't care where the workflow completes, so it uses a type variable'Post
. - The
AsyncDownloadString
operation is implemented such that it returns back to the original thread where it was started. This is behavior used by most of the standard F# asynchronous operations and is captured by the fact that the same type variable ('T
) is used for both starting and finishing thread.
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 SynchronizationContext.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 background 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 SynchronizationContext<'Thread>
.
The type parameter 'Thread
represents the thread captured by the context.
When getting the current context, we use an operation CurrentContext
provided 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
SynchronizationContext<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 SynchronizationContext<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 synchronization 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:
- The
Return
operation creates an asynchronous workflow that immediately returns the given value. It doesn't interact with threads in any way - the workflow will finish on the same thread where it was started. - The
Bind
operation is interesting. It takes a workflow of typeCheckedAsync<'Pre, 'Interm, 'V1>
as the first argument. In order to produce the result, it needs to evaluate this workflow first. This means that the computation will start on the'Pre
thread and end on the'Interm
thread. Then the operator calls the provided function and gets a workflow of typeCheckedAsync<'Interm, 'Post, 'V2>
. This workflow can be executed after the first one because the ending thread of the first one matches the starting thread of the second. After executing, the result will be reported on the thread'Post
, so the overall computation starts on'Pre
and ends on'Post
(which is reflected by the returned workflow type). - The snippet also shows the type of
While
, which takes a function that is called to test whether a condition holds and the (asynchronous) body. When the loop executes the body repeatedly, the body must end at the same thread where it started. This directly follows from the type ofBind
and the wayBind
can be used to implementWhile
. The typing rule for theFor
operation would be similar.
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
- Browse the source code on GitHub
- Download the source code (ZIP)
References
- [1] Hoare logic - Wikipedia
- [2] Parameterised Notions of Computations - Robert Atkey
- [3] Beyond Monads - A Neighborhood of Infinity
- [4] Lightweight monadic regions - Oleg Kiselyov, Chung-chieh Shan
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
type GuiThread =
interface
end
Full name: Demo.CheckedAsync.GuiThread
--------------------
GuiThread
type BackgroundThread =
interface
end
Full name: Demo.CheckedAsync.BackgroundThread
--------------------
BackgroundThread
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: FSharp.CheckedAsync.Values.asyncOn
Full name: FSharp.CheckedAsync.Values.background
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
Full name: FSharp.CheckedAsync.Values.gui
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.wrongWorkflow
Full name: FSharp.CheckedAsync.Values.asyncChecked
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
CheckedAsync<BackgroundThread,'a,'b>
but given a
CheckedAsync<GuiThread,GuiThread,unit>
The type 'BackgroundThread' does not match the type 'GuiThread'
Full name: Demo.StandardAsync.goodWorkflow
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
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
Full name: Demo.StandardAsync.main
[ for stock, btn in Seq.zip stocks buttons ->
btn.Click |> Observable.map (fun _ -> stock) ]
|> List.reduce Observable.merge
type: string
implements: IComparable
implements: ICloneable
implements: IConvertible
implements: IComparable<string>
implements: seq<char>
implements: Collections.IEnumerable
implements: IEquatable<string>
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: 'a
implements: seq<float>
implements: Collections.IEnumerable
CheckedAsync<GuiThread,'a,'b>
but given a
CheckedAsync<BackgroundThread,BackgroundThread,float []>
The type 'GuiThread' does not match the type 'BackgroundThread'
Full name: Demo.StandardAsync.Switching.main
Full name: Demo.StandardAsync.StartChild.main
type CheckedAsync<'Pre,'Post,'Value> = | SA of Async<'Value>
Full name: FSharp.CheckedAsync.CheckedAsync<_,_,_>
--------------------
type CheckedAsync
Full name: FSharp.CheckedAsync.CheckedAsync
Full name: FSharp.CheckedAsync.Extensions.SwitchToGui
Continue executing on the GUI thread (regardless of the current thread)
Full name: Microsoft.FSharp.Core.unit
type: unit
implements: IComparable
type GuiThread =
interface
end
Full name: FSharp.CheckedAsync.GuiThread
--------------------
GuiThread
Full name: FSharp.CheckedAsync.Extensions.AwaitObservable
Waiting for event occurrence is allowed on GUI thread only
type IObservable<'T> =
interface
member Subscribe : System.IObserver<'T> -> System.IDisposable
end
Full name: System.IObservable<_>
--------------------
IObservable
Full name: FSharp.CheckedAsync.Extensions.StartImmediate
Starts a computation on the (current) GUI thread
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
Full name: FSharp.CheckedAsync.Extensions.AsyncDownloadString
Downloads string and returns to original thread
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 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>
Full name: FSharp.CheckedAsync.Extensions.CurrentContext
Get current context (the thread is also kept in the type)
Full name: FSharp.CheckedAsync.SynchronizationContext<_>
Full name: FSharp.CheckedAsync.Extensions.SwitchToThreadPool
Switch to new background thread (from any thread)
type BackgroundThread =
interface
end
Full name: FSharp.CheckedAsync.BackgroundThread
--------------------
BackgroundThread
Full name: FSharp.CheckedAsync.Extensions.SwitchToContext
Switch to thread represented by a synchronization context
Full name: FSharp.CheckedAsync.Extensions.StartChild
Start child on a background thread (without changing current thread)
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
Full name: FSharp.CheckedAsync.CheckedAsyncBuilder.Return
Return creates async workflow that resumes on the same thread
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)
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)
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