About the book
This is a sample chapter of the book
Real-World Functional Programming. It has been published with the exclusive
permission of Manning.
Tomas Petricek with Jon Skeet
Get 30% discount
DotNetSlacker readers can get 30% off the full print book or ebook at
www.manning.com using the promo code dns30 at checkout.
When designing applications that don't react to external events, you have lots of control flow constructs available, such as
for loops and
while loops in imperative languages, or recursion and higher-order functions in functional languages. Constructs like this make it easy to describe what the application does. The control flow is clearly visible in the source code, so drawing a flowchart to describe it is straightforward.
Understanding reactive applications is much more difficult. A typical C# application or GUI control that needs to react to multiple events usually involves mutable state. When an event occurs, it updates the state and may run more code in response to the event, depending on the current state. This architecture makes it quite difficult to understand the potential states of the application and the transitions between them. Using asynchronous workflows, we can write the code in a way that makes the control flow of the application visible even for reactive applications.
Waiting for events asynchronously
The reason why we can't use standard control flow constructs to drive reactive applications is that we don't have any way of waiting for an event to occur. Writing a function that runs in a loop and checks whether an event has occurred is not only difficult to implement, it's also very bad practice: it would block the executing thread. As you learned in chapter 13, asynchronous workflows allow us to write code that looks sequential, but which can include waiting for external events (such as the completion of an asynchronous I/O operation) but is executed asynchronously without blocking the thread.
So far, we've seen only asynchronous methods that perform I/O operations, but we can also define a primitive that stops the asynchronous workflow and resumes it when the specified event occurs. The primitive, called
AwaitObservable, is available in the online source code for the book as an extension for the
Async type. Let's start by looking at its type signature:
The type shows us that the function is quite simple. It takes event as an argument and returns a value that we can use inside an asynchronous workflow using the
let! keyword. One important difference between events and
Async<'T> values is that an asynchronous workflow can be executed at most once, while events can be triggered multiple times. This means that the
AwaitObservable function has to wait only for the first occurrence of the event and then resumes the asynchronous workflow. Let's take a look at how we can use
AwaitObservable in a GUI application.
Counting mouse clicks
We'll start by implementing an example similar to the counter increment/decrement application we used to demonstrate the
Observable module higher-order functions. This will be simpler: it counts the number of clicks that take place and displays the count on a label. This behavior could be implemented using
Observable.scan and the source code would be shorter, but as we'll see later
AwaitObservable is a far more powerful construct. The following listing shows to write event handling code using asynchronous workflows.
Listing 1: Counting clicks using asynchronous workflows
The essential part of the application that implements the counting is a single recursive function that's implemented as an asynchronous workflow. The function appears to create an infinite loop, which sounds suspicious to start with. The construct is completely valid, because it starts by waiting for a
MouseDown event. This is done asynchronously, which means that the workflow will install the event handler and the rest will only be executed when the user clicks the label. Once the event occurs, we update the text and loop with the incremented counter.
Earlier we mentioned that the
AwaitObservable primitive waits for the first occurrence of the event, because asynchronous workflows can yield only a single value. As you can see in this example, if we want to handle every occurrence of the event, we can simply use a recursive loop to wait for the next occurrence. Using recursion also allows us to store the current state in the function parameters. In fact, this technique for expressing computations is similar to the primitive recursive functions we saw earlier in the book.
When working with Windows Form controls, we're required to access them only from the GUI thread, which is the main thread of the application. When you try to use property from other threads, the behavior is undefined and the application could crash. This means that we need to make sure that the asynchronous workflow will be executed only on the GUI thread. So far, we didn't really care where the workflow executes, but F# libraries provide a mechanism to control that.
First, most of the asynchronous operations return to the calling thread after completion. This means that when you invoke an operation such as
Async.Sleep, the operation will release the calling thread and start executing in background. When it completes (usually on some background thread), it will use the .NET
SynchronizationContext class to return to the thread where it was started.
When we start the workflow on the GUI thread, it will continue running on the GUI thread even if the workflow includes some operations that involve background threads. Thanks to this behavior, we can safely access Windows Forms controls from any part of the workflow. The only remaining question is how we can start workflow on the GUI thread. In listing 16.9, we use the
Async.StartImmediate primitive (#4), which runs the workflow on the current thread. When the application starts, the current thread will be the main GUI thread.
The following figure shows what happens when we use the
StartImmediate primitive to run a workflow that contains a call to
AsyncGetResponse. The most important fact is that when we run an asynchronous operation (using the
let! primitive), the GUI thread is free to perform other work. When the workflow running on a GUI thread spends most of the time waiting for completion of an asynchronous operation, the application won't become unresponsive.
StartImmediate starts the workflow on a GUI thread. The
AsyncGetResponse operation runs in the background, while the GUI thread can perform other work. When the background operation completes, the workflow returns to the GUI thread.
As we said earlier, we could easily have implemented this example using
Observable.scan; let's look at a slightly more complicated problem.
Limiting the speed of clicks
Let's say that we'd like to limit the rate of clicks. We want the count to stay the same at least for one second after it gets incremented by the user clicking on the label. One way for implementing this is to add another parameter to the
loop function of type
DateTime that will store the last time of a successful click. When the event occurs inside the loop, we could then check the difference from the current time and the last time and increase the count only when the difference is larger than the limit.
There's a much simpler way of achieving this. In chapter 13 we discussed the
Thread.AsyncSleep method, which allows us to stop the workflow for a specified time. If we use it somewhere in the loop function, it will sleep for one second before reacting to the next event, which is exactly what we wanted. All we have to do is to add the following single line before the line that last line that runs the recursion:
This is already something that would be quite difficult to do using the functions from the
Observable module. If you're curious, you can find the solution using
Observable functions in the source code at this book's website; it's about 8 lines long and a bit tricky to understand. The control flow of this example was still pretty simple. In the next section, we'll explore a more sophisticated example that better demonstrates the capabilities of using asynchronous workflows for GUI programming.
One problem that's surprisingly difficult to solve in a functional way is drawing graphical objects on a Windows Forms control. Suppose we want to draw a rectangle so that the user starts by pressing the mouse button in one of the corners, moves the cursor to the opposite corner, then releases the button. While moving the cursor with the button pressed, the application should draw the current shape of the rectangle, and when the button is released, it should be finally applied to a bitmap or stored in the list of vector shapes.
A typical imperative implementation would use a mutable flag specifying whether we're currently drawing and a mutable variable to store the last location where the user pressed the mouse button. Then we'd handle
MouseDown and MouseMove events and modify the state appropriately when one of them fired. We can check whether the drawing is finished in the handler for the
MouseMove event, because it also carries information about the state of mouse buttons. Alternatively, we could use the
MouseUp button, but the first version will be easier to start with. If we think of the control flow of the application, we can see that it's quite simple. Here is a flowchart that demonstrates this logic:
When the application is
Waiting, we can press the button to start
Drawing. In this state, we can either continue
Drawing by moving the mouse or complete the task and change the state of the application back to
Waiting by releasing the button.
We're almost ready to convert this state machine into an F# program using asynchronous workflows, but first we need a form to draw on and a utility function to help us with the basic task of drawing a rectangle.
Implementing program fundamentals
We'll improve this application later, but let's start with an empty form on which we can draw rectangles. The following listing shows the code required to create the form and a function,
drawRectangle, that draws a rectangle on the form using the specified color and two of any corner points of the rectangle.
Listing 2: Creating a user interface and drawing utility
The code is very straightforward. The function
drawRectangle takes all its arguments as a tuple, so it can be used in a way that's consistent with calling .NET methods. In addition, its second and third parameters are nested tuples that represent the X and Y coordinates of the corners of the rectangle. This makes the rest of the code easier.
Implementing the state machine
Now that we have all the basics of the application, we can implement our user interaction. We're going to follow the state machine described in the figure shown earlier, with two states (
Drawing) that have various transitions between them. Asynchronous workflows allow us to translate this directly, representing each state with a single function. The transitions can be encoded as function calls or by returning a value from a function.
For our example this means that we'll have two functions called
waitingLoop. The first of these also needs to remember some state, which we represent using the function's parameters.
Listing 3: Workflow for drawing rectangles
The most direct way to encode the state machine would be to use recursive calls between the two functions using the
return! keyword. Listing 16.11 makes a minor change to this, to aid readability. The
waitingLoop function contains an infinite
while loop that waits until the user clicks the left button, then transfers control to the
drawingLoop function. When
drawingLoop completes, it returns the end position of the rectangle and transfers the control back to
waitingLoop. We can then print the information about the drawn rectangle and wait for another
The function that runs while the user is drawing a rectangle is looping using recursive calls, because it needs to keep some state.It starts by waiting for the
MouseMove event, which is also triggered when the button is released. It then tests whether the button is currently pressed; if that's the case, it refreshes the view of the form. This transition corresponds to the arc looping in the
Drawing state. When the button is released, it returns the last location as a result, which is the transition back to the
That's almost everything we need to run the application. All that remains is to start the asynchronous workflow that handles drawing of rectangles and run the application. We'll use the
Async.StartImmediately primitive to start the workflow on the GUI thread:
In this simple application, we need only a single asynchronous workflow that handles all the interaction with the application, but multiple workflows can be combined easily. If we wanted to allow polygons to be drawn using the right mouse button, we could implement that without making any changes to the existing code. We'd simply create another workflow for drawing polygons and start it independently using
Async.StartImmediately. This way of writing the UI code gives us a modular way of splitting complex interactions into separate processes.
Running workflows on the GUI thread
The application we just implemented consists of a single running process, but it's important to realize that a process in the sense we're using here doesn't correspond to a thread. Even if we had multiple processes waiting for GUI events, the application would still be single-threaded.
When we run the
Async.StartImmediately method in the earlier example, it starts running the workflow and doesn't complete until the workflow reaches a point where it waits for a completion of an asynchronous operation (such as waiting for an event). Both
Async.AwaitObservable, which we've used so far, return to the caller thread after completion, so the workflow will continue running exclusively on the GUI thread.
Even if we add multiple processes that wait for UI events, this technique doesn't introduce any parallelism. All the code runs on the GUI thread, and if a single event causes state transition in multiple processes, the bits of workflows are executed sequentially. Using asynchronous workflows to write UIs gives us an easier way to write our single-threaded GUI processing.
Later on, we'll see a technique that allows us to integrate this form of GUI processing with other processes that can potentially run in parallel. However, code for user interface interaction like this should be simple and shouldn't perform any complicated computations, so there's no need for parallelism. Even when we need to perform some time-consuming computations for the GUI, it's still a good idea to move all this work to a background worker thread.
The code we've written so far isn't a drawing application, because it doesn't store the rectangles we've drawn. Once a rectangle has been completed by the user releasing the button, it prints information to the console and forgets the rectangle. We could store a list of rectangles as a parameter of the
waitingLoop function (if we made it a recursive function), but that would cause other problems. The list would be private to the drawing loop, so it couldn't be accessed from other parts of the application. We need a different approach to handle global state that's used by the whole application.
In this article, we've explored a powerful programming idiom based on F# asynchronous workflows. We've seen that we don't have to handle all events by attaching a handler to the event and then modifying global state of the application (or a control) when the event occurs. Instead, we can use asynchronous workflows and write code that waits for events, resumes performs some action and then waits for another event. This programming model is very comfortable, because we can use high-level language constructs such as (imperative) loops or (functional) recursion to express control flow logic such as drag&drop algorithm.
Get 30% discount
DotNetSlacker readers can get 30% off the full print book or ebook at
www.manning.com using the promo code dns30 at checkout.
Manning Publication publishes computer books for professionals--programmers, system administrators, designers, architects, managers and others. Our focus is on computing titles at professional levels. We care about the quality of our books. We work with our authors to coax out of them the best writi...
This author has published 33 articles on DotNetSlackers. View other articles or the complete profile here.
Please login to rate or to leave a comment.