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
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.
Writing non-blocking user-interfaces in F#
F# asynchronous workflows are best known as a way to write efficient I/O operations
or as an underlying mechanism of F# agent-based programming (using the
type). However, they are also very useful for user-interface programming. I think this is
a very interesting and important area, so I already wrote and talked about this topic -
it is covered in Chapter 16 of my book (there
is a free excerpt)
and I talked about it at F#unctional Londoners
Many applications combine user-interface programming (such as waiting for an event asynchronously)
with some CPU-intensive tasks. This article looks at an example of such application and I'll explain
how to avoid blocking the user-interface when doing the CPU-intensive task.
The article starts with an example that is wrong and blocks the user-interface when doing data processing.
Then I'll show you two options for fixing the problem. The three most important
functions from the standard F# library that I'll discuss are
This is the first article of a mini-series. In the next article, I'll demonstrate a simple
wrapper for F#
async that makes it more difficult to write wrong
programs. The wrapper keeps the desired thread (GUI or background) in the type of the
computations and code that would block the user interface will not type-check. But first,
let's look at the example...