Published: 16 Feb 2010
By: Manning Publications

F# asynchronous workflows can be used to solve a wide range of programming problems. In this article we'll look how to use asynchronous workflows for elegantly expressing the control flow of interaction with the user. We'll also look at clear functional way for encoding drag&drop-like algorithm.

Contents [hide]

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.


Written by: Tomas Petricek with Jon Skeet
Pages: 560
Publisher: Manning
ISBN: 9781933988924

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.

Introduction

When designing applications that don't react to external events, you have lots of control flow constructs available, such as if-then-else expressions, 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 AsyncGetResponse, AsyncRead, or 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.

Drawing rectangles

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 (Waiting and 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 drawingLoop and 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 MouseDown event.

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 Waiting state.

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.Sleep and 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.

Summary

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.

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

About Manning Publications

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.

Other articles in this category


Android for .NET Developers - Location and Maps
In Windows Phone and iOS getting the current position of the device in terms of latitude and longitu...
Android for .NET Developers - Using Web Views
In this article, I'll show a native app that contains a web-based view. The great news is that HTML ...
Android for .NET Developers - Building a Twitter Client
In this article, I'll discuss the features and capabilities required by an Android application to ta...
Developing a Hello World Java Application and Deploying it in Windows Azure - Part II
In this article we will see the steps involved in deploying the WAR created in the first part of thi...
Ref and Out (The Inside Story)
Knowing the power of ref and out, a developer will certainly make full use of this feature of parame...

You might also be interested in the following related blog posts


Adding users to a TFS project when youre not on the domain read more
Creating a Filtering User Interface With jQuery In a Web Forms Application: Part 1 read more
Unit Testing - Do Repeat Yourself read more
Update to Logging in to DotNetNuke from a Silverlight Application with RIA Authentication read more
Session State Not Working? Check Your Web Garden! read more
Telerik Announces Support for Microsoft Silverlight 3 read more
Principle of Least Surprise read more
Telerik Introduces Free Web Testing Framework for ASP.NET AJAX and Silverlight read more
Examining ASP.NET's Membership, Roles, and Profile - Part 15 read more
Customizing ASP.NET's CreateUserWizard Control To Display a Fixed Set of Security Questions read more
Top
 
 
 

Discussion


Subject Author Date
placeholder more events? Alexey Ary 5/27/2010 8:36 AM

Please login to rate or to leave a comment.