An Introduction To F# Agents

Applications that perform just a single task at a time are becoming quite rare these days. Programs need to handle inputs from the user-interface, while communicating with a web service and performing some computation in the background. There are also numerous kinds of applications that are inherently concurrent: servers need to handle multiple requests from different clients and trading applications need to process incoming requests from various sources.

Concurrent applications have quite specific requirements…they need to have required throughput, should be always able to accept another input and they need to scale to a large number of concurrent tasks. Developing such applications using a traditional architecture is difficult. Using threads can easily lead to race conditions or deadlocks and blocking threads limits scalability.

In this article, I’ll briefly introduce F# agents. Agents are the F# implementation of an approach that was first used in Erlang (known as message-passing) and can be now found in many other languages including Scala and Clojure (known as agents or actors). In F#, agents are asynchronous, which means that they do not block a thread. This makes the design scalable…an application can easily consist of hundreds of thousands of agents. At the high-level, agents make it easier to write distributed and fail-safe applications.

Parallel, Asynchronous and Concurrent Programming

There are three key terms that we need to know the difference between when talking about concurrent architectures: parallel programming, asynchronous programming and concurrent programming. The relationship between the three terms is shown in Figure 1.

Parallel, Asynchronous and Concurrent Programming

Figure 1. Parallel, asynchronous and concurrent programming

Parallel Programming.
In parallel programming, multiple calculations are carried out simultaneously. The computations are typically CPU-intensive and perform either different operations on the same data set or perform the same operation on parts of a data set. The main goal of parallel programming is to make CPU-intensive computations faster by using multiple cores or processors.
Asynchronous Programming
The term asynchronous is usually used when describing I/O operations that do not block a thread while waiting for completion (e.g. a response from a web server or the completion of a disk operation). By avoiding blocking threads, threads are free to do other work or accept inputs while I/O operation is carried out, which helps make applications scalable. However, asynchronous code is not necessarily parallel…it can be fully sequential; it just does not block threads. Until recently, asynchronous programming was typically implemented using callbacks or events, but F# offers an easier option.
Concurrent Programming
In concurrent programming, programs are designed as computations that interact. The computations may be executed on multiple threads (in parallel), but also using just a single thread. The main focus of concurrent programming is on the interaction between the processes, which can be implemented using shared memory and synchronization primitives (such as locks) or using message passing.

It is important to understand that concurrent applications don’t have to be parallel. The computations that form a concurrent system may all run on a single thread and may not actually run simultaneously (i.e. when waiting for some input). Similarly, running I/O operation on a separate thread does not make it asynchronous…if the computation is started and given a callback, it will not block the original thread, but it will block another one. This may be fine for an end-user program (the GUI is not blocked while performing I/O), but it can limit the scalability of a server-side application.

Introducing F# Agents

In this article, we look at the intersection of the three programming styles. F# agents can be used to write concurrent applications. The individual computations of the application are represented using agents that communicate using messages. When an application handles some user-interface event, receives a reply from a web service or finishes a computation, it sends a message to some agent, which then updates its state and possibly sends more messages to perform some work.

F# agents are a way to design concurrent systems, but they are also both asynchronous and parallel. This has two implications:

  • By being asynchronous, the agents are lightweight, because they do not block threads while waiting for a message. As a result, it is possible to use hundreds of thousands of agents in a single application.
  • When multiple agents need to react to a message, they are executed in parallel. This means that the performance of a well-written agent-based application scales with the number of cores or processors.

In the rest of the article, we look how agents are usually written. As usual in in F#, the first step is to consider the types involved…in case of agent, the type represents the messages that an agent can handle.

Defining Messages

An F# agent is like an object with an asynchronous computation that runs “inside” the object.

  • It communicates with other agents by sending and receiving strongly typed messages. They are strongly typed so it can be guaranteed that an agent can handle all the messages it receives.
  • Message processing and other functionality related to the agent is implemented in the asynchronous computation that forms the body of the agent. An agent can also call various external functions, but these cannot modify the local state of the agent, because the state is local to the agent.
  • An agent reacts to messages received by updating their local state or sending messages to other agents.

Typically, a message type needs to represent several kinds of messages. In object-oriented programming, this could be done using an abstract base class Message and a derived class for every kind of message. In F#, the same thing can be done easily using a discriminated union type.

To demonstrate, we’ll spend the rest of this article building a simple agent that calculates the average of a series of numbers sent to it. We start by defining the type CounterMessage (Listing 1) to represent two messages…one for sending numbers to an agent and one for resetting it.

Listing 1. A discriminated union representing two messages

type CounterMessage = 
  | Update of float
  | Reset

The definition of a message type is the most important part of implementing an agent. The message type describes the interface of the agent. Once that it done, we need only to implement the code that handles the different types of message and the internal processing within the agent. This resolves into two tasks:

  • Calculating the average value from Update messages.
  • Starting the count of numbers again by resetting the information about values received when the Reset message is received.

Implementing The Agent

The implementation of the agent is shown in Listing 2. The agent is created (and started) using a static method MailboxProcessor.Start (often, F# programmers define a type alias Agent instead of MailboxProcessor). The method takes a function that returns the body of the agent. When called, the function gets a parameter…called inbox in the code sample…that represents the created agent and can be used to wait for the next message.

Listing 2. Simple agent that calculates average and can be reset.

let inbox = MailboxProcessor.Start(fun agent -> 

// Function that implements the body of the agent
let rec loop sum count = async {
  // Asynchronously wait for the next message
  let! msg = agent.Receive()
  match msg with 
  | Reset -> 
      // Restart loop with initial values
      return! loop 0.0 0.0

  | Update value -> 
      // Update the state and print the statistics
      let sum, count = sum + value, count + 1.0
      printfn "Average: %f" (sum / count)

      // Wait before handling the next message
      do! Async.Sleep(1000)
      return! loop sum count }

// Start the body with initial values
loop 0.0 0.0)

All of Listing 2, except for the first line, is an expression that defines the body of the agent. The expression first defines an asynchronous function loop and then calls the function with two numbers representing the initial state of the agent. The result of calling loop 0.0 0.0 is an asynchronous computation that is started inside the agent.

The Body of the Agent

The agent is written in a functional way, which means that it uses recursion to hold local state. The state is formed by the sum of all updates received so far and the total number of Update messages. It is maintained in the parameters of the loop function. After handling a message, the asynchronous body of the agent calculates new values of sum and count and calls the loop function recursively with the new values as arguments.

N.B. It is also possible to implement this in an imperative style rather than functionally as we have done here. This option comes in handy if you want to use some efficient imperative data type, such as a hashtable in your computation. Some examples of this can be found in Don Syme’s blog post on agent-based patterns

The body of the loop function (lines 4-19), which implements the agent’s behavior, is wrapped in an async block. This means that it can contain long-running operations that do not block a thread (more information about how F# asynchronous workflows work can be found in the MSDN documentation). The async block can contain normal F# expressions and a few additional constructs for asynchronous operations. When running, the body behaves as follows:

  • It starts by calling agent.Receive() (line 6) using the let! construct (pronounced let bang). This is an asynchronous call that waits for a message from the agent and then resumes the workflow. As already mentioned, this waiting is done without blocking a thread.
  • When resumed, the workflow uses pattern matching to handle different kinds of messages. Messages are represented as discriminated unions, so the function uses the match construct (line 7) to extract arguments of the messages (i.e. value) and decide which part of code should execute.
  • When the agent receives Reset (line 8) it uses return! (line 10) to recursively execute the loop function. The call using return! is tail-recursive, which means that the recursion can be translated more efficiently (and there is no danger of stack overflow or memory leaks).
  • When the agent receives Update (line 12), it calculates a new state, prints it and then asynchronously waits for 1 second using the do! construct (line 18). This is similar to let!, but is used for operations that do not return a result. After waiting, the loop function recursively calls itself with the new state and is ready to handle more messages (line 19).

Testing The Agent Interactively

The agent is very simple, but it demonstrates several important principles of the F# programming style and the agent-based architecture. The method MailboxProcessor.Start returns a running agent. The object inbox represents the agent and provides various methods for sending messages to the agent. In this article, we use just a simple Post method that sends a message to the agent without waiting for a reply. You can see code that sends several messages to the agent in Listing 3.

Listing 3. Sending messages to the agent

counter.Post(Update 10.0)
counter.Post(Update 5.0)
counter.Post(Reset)
counter.Post(Update 10.0)

If you want to experiment with the agent using Visual Studio, you can go to File -> New -> File and select F# Script File in the Script category. Then you can copy the code from F# Snippets and run it in the F# Interactive. To do that, select the lines you want to execute (such as the definition of the agent) and hit Alt+Enter. Alternatively, you can also run the agent interactively in your web browser by loading the sample in Try F# web site (requires Silverlight or Moonlight). On the Try F# web site, you can run blocks of code using Ctrl+Enter.

When you execute the first line of Listing 3, the message Update 10.0 is sent to the agent. The body of the agent resumes executing and pattern matches on the message. It goes into the second branch, prints the newly calculated average and then blocks for 1 second before waiting for the next message. The agent uses this to limit the processing rate to one message per second. Here are several important properties of the agent:

Buffering of messages.
If you send multiple Update messages to the agent, none of the messages will get lost. The messages are buffered…when the agent calls Receive and there is a message in the buffer, it just picks a message from a buffer and processes it immediately.
Asynchronous execution.
The waiting for a message using Receive in the agent’s body is asynchronous. If you create an array containing hundreds of agents, the F# Interactive process (fsi.exe) will not use hundreds of threads. When the Post method sends a message to the agent and the agent is waiting, it schedules the work to be done in a thread pool.
Mutable and immutable state.
The counter agent keeps immutable state using recursion, but it is also possible to write agents using mutable types. There is a notable difference…mutable types can be safely used inside the agent, but immutable types can also be safely passed out of the agent!
For example, imagine that the agent would build an immutable list of all updates received so far. Sending the list to other agents would be perfectly fine, because it cannot be modified. When writing agents with mutable state, we need to be more careful to avoid race conditions.
The counter agent is also always able to handle all messages it receives, but it is possible to write agents with multiple states that can only handle certain messages in certain states. For example, an agent that implements a blocking queue cannot handle a message that adds an item when the queue is full.

The counter agent developed in this article is quite simple, but it demonstrates some of the basic principles of agent creation. For more information about developing F# agents, you can go to a Tutorial on MSDN. Examples of more complex agents are also available in the F# Documentation on MSDN.

Summary

This article is the first part of a two-part series. So far, we have looked at F# agents, which provide an elegant way to write concurrent applications. The combination of immutable data types (common in functional programming) and thread-safe agents gives an easier way to write concurrent programs without race conditions and deadlocks. This can significantly reduce the cost of developing and maintaining solutions. Moreover, thanks to the use of F# asynchronous workflows, agent-based applications are scalable, because agents do not block physical threads when waiting.

We looked at an agent that counts the average value from the received numbers. Although simple, the agent demonstrates several important properties. Most importantly, we’ve seen that agents are asynchronous and buffer received messages in a thread-safe way. If you want to learn more about agents, the best resource is the MSDN Tutorial that uses agents to implement online chat [MSDN2011a].

In the next article of the series, I will discuss concurrent systems from a higher-level perspective. You’ll learn about composing concurrent systems from multiple different agents and about the common communication patterns that are often used in agent-based systems.

You might also like...

Comments

About the author

Tomas Petricek

Tomas Petricek United Kingdom

Tomas is a long time F# enthusiast, using F# since the early Microsoft Research versions. He has been Microsoft MVP since 2004, and together with Jon Skeet wrote Real-World Functional Programmin...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“The greatest performance improvement of all is when a system goes from not-working to working.” - John Ousterhout