Overview: Server-Side Programming with F# Agents
Applies to: Functional Programming
Summary: This article reviews several technologies that can be used for writing server-side and concurrent systems in F#.
This topic contains the following sections.
- Programming for the Server Side
- Technologies for Server Programming
This article is associated with Real World Functional Programming: With Examples in F# and C# by Tomas Petricek with Jon Skeet from Manning Publications (ISBN 9781933988924, copyright Manning Publications 2009, all rights reserved). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.
Programming for the Server Side
The shift towards the software as a service paradigm means that an increasing number of applications need to be written as servers that expose functionality to lightweight clients via the network. Server-side applications include web servers, web services, data providers, as well as specialized software such as application components running in the cloud.
In many ways, server-side software is different from applications that run on the client. The most important differences are:
Importance of reliability and scalability. When developing server-side applications, an extreme effort is needed to ensure that applications don't stop working, for example, when the connection with the client is aborted. The throughput of the applications also needs to scale as new clients start using the service.
Concurrent requests. A server-side application needs to be designed in a way that enables handling multiple requests concurrently. It is no longer possible to write an application as a program with a single sequential control flow.
I/O bound operations. Many server-side applications need to work with data from other sources such as databases, the file system, or third-party web services. Accessing these sources may occupy the largest portion of the request-processing time. This also applies to receiving input from the client and sending the response.
Shared state. Servers often exist to enable state sharing among a large number of clients. Examples include collaborative systems such as instant messaging or tools for collaborative content editing. This means that concurrently running processes in a server-side application need to be able to safely access shared states.
This list shows that server-side programming has many different specifics. The F# language and libraries provide elegant ways to write applications with the listed properties. The rest of this document gives a brief overview of the key technologies.
Technologies for Server Programming
This section looks at several technologies that are useful for developing server-side and concurrent applications in F#. A larger system will probably combine several of them for the application's different components or subsystems. Some of the technologies are also very closely related.
For example, agent-based programming in F# is based on asynchronous workflows. This means that, when using agents, the code will also use asynchronous workflows. The opposite is not true though. Simple scenarios can be solved using asynchronous workflows without the power provided by F# agents.
Using Asynchronous Workflows
Asynchronous workflows provide a way to write sequential computations that run without blocking system threads. For example, receiving data from a web service may be done in a loop that downloads data in chunks. A loop is a sequential construct, but the application shouldn't block the thread while waiting for the next chunk of data.
Asynchronous workflows are an essential part of the server-side programming model in F#. When writing code using asynchronous workflows, it is possible to use exception handling constructs as usual, so it is easy to write reliable code. At the same time, asynchronous workflows enable concurrent processing of requests without limiting the scalability by blocking an unnecessary number of threads.
The following list summarizes situations where asynchronous workflows are essential:
Asynchronous workflows offer great scalability for I/O operations. For example, communication using web services or sockets should always be done asynchronously.
Asynchronous workflows can be used to express sequential long-running computations. For example, performing an action every hour can be written as an asynchronous loop.
Asynchronous workflows can be parallelized using Async.Parallel and Async.StartAsChild. This is preferred over tasks (see below) when the built-in support for cancellation is needed or if the computations are not purely CPU bound.
Asynchronous workflows are useful for gluing other operations together. They can wait for events using Async.AwaitEvent and for completion of tasks using Async.AwaitTask. Asynchronous operations exposed by agents can also be called using the let! construct.
For more information about asynchronous workflows see:
Asynchronous Workflows (F#) explains how to use F# asynchronous workflows for writing efficient computations that do not block the execution of other work.
Book Chapter 13: Asynchronous and data-driven programming explains how asynchronous workflows work and uses them to write an interactive script that downloads large datasets from the Internet.
Async and Parallel Design Patterns in F#: Parallelizing CPU and I/O Computation gives an overview of common programming patterns based on F# asynchronous workflows.
F# Parallel Extras (III.): Financial dashboard with cancellation (Tomas Petricek's blog) presents a larger sample application that demonstrates the F# asynchronous workflows support for cancellation.
Control.Async Class (F#) provides documentation about the Async module that includes operations such as Async.Parallel, Async.AwaitTask, and Async.AwaitEvent.
Using Message Passing with Agents
In the agent-based programming model, applications are written in terms of agents that communicate by sending messages to each other. An agent is a lightweight process that usually contains a loop that waits for a message, processes the message, and then starts waiting for the next message. Sending a message to an agent is a thread-safe operation, so agents are a great fit for concurrent systems. The messages sent to an agent are queued so an agent will eventually handle all messages that are sent to it.
The body of the agent is written as an asynchronous workflow, which means that an agent can wait for messages or perform other operations without blocking system threads. This also means that agents do not require a large amount of system resources. When using the agent-based programming model, it is perfectly possible to create systems that use thousands of agents.
Agents may be a good choice in the following situations:
Agents are a great fit when creating a component that stores some state and needs to be accessed from multiple threads or multiple concurrently executing asynchronous workflows. Agents can encapsulate the state using functional style, but make it accessible in a thread-safe way.
Applications that are written as a large number of agents can easily benefit from multi-core systems because all agents can potentially run in parallel.
For more information about agent-based programming in F#, see:
Tutorial: Creating a Chat Server Using Mailboxes develops a sample application based on agents, starting from a simple agent and ending with a working HTTP web server.
How to: Create Reusable Agents discusses how to implement common concurrent design patterns using F# agents.
Async and Parallel Design Patterns in F#: Agents (Don Syme's blog) provides an overview of various options for implementing agents and for representing the state inside agents.
Control.MailboxProcessor<'Msg> Class (F#) provides a reference for a type that represents agents in F#. (We usually define a convenient type alias Agent<'T>.)
Programming with First-Class Events
The F# language treats .NET events as values of type IEvent<'T> and provides a rich programming model built on top of events. This interface inherits from the IObservable<'T> type that is available in Microsoft® .NET 4.0 and is used by other libraries such as Reactive Extensions (Rx).
When writing a type that exposes some notifications, new event values are created using the concrete type Event<'T>. Events can be handled using standard library functions to filter events or transform the values carried by events.
The event-based programming model has a minimal overhead. When triggering an event, the registered handlers are invoked synchronously on the current thread. This is very efficient for simple tasks, but we cannot easily perform asynchronous operations when working with events.
Here are some scenarios when events are the right abstraction:
Events provide a way to implement push-based notifications. When a component needs to report some change in the state or a progress in computation, it can expose it as an event.
F# library functions from the Event module make it easy to implement declarative event processing such as filtering, value transformations, event merging, and state aggregation. This is useful, for example, for combining messages from various sources.
For more information about programming with events and observables:
Book Chapter 16: Developing reactive functional programs discusses how to use the event-based programming model available in F# and how to combine it with asynchronous workflows.
How to: Create an Agent for Batch Processing demonstrates how to use events for exposing notifications and how to implement simple declarative processing of events.
Async and Parallel Design Patterns in F#: Reporting Progress with Events (Don Syme's blog) discusses several F# programming patterns based on events.
Control.Event Module (F#) provides documentation for event-processing functions available in the F# library.
Reactive Extensions for .NET (Rx) is a more sophisticated library for declarative event processing that can be also used for event-processing in F#.
Using Parallel Extensions to .NET
As discussed earlier, server-side applications need to handle a large number of requests in parallel. A server-side application that runs efficiently on modern systems needs to exploit the power of multicore CPUs. This can be done using asynchronous workflows and agents. Microsoft® .NET 4.0 also provides the Task Parallel Library (TPL).
The agent-based programming model discussed in Using Message Passing with Agents is inherently concurrent but it requires architecture that differs from the usual sequential style. F# asynchronous workflows are designed to allow nonblocking execution of sequential code, but they also support parallel programming (using Async.Parallel and Async.StartAsChild).
The libraries provided by F# are generally the best way for designing server-side applications in F#, but there are several situations when Task Parallel Library may be worth considering in F#:
When working with very large datasets in memory, the Parallel LINQ (PLINQ) can automatically parallelize data processing code. This can be done using the PLINQ methods directly or by using the PSeq module available in the F# PowerPack.
If a server or a component needs to perform a computationally expensive operation that doesn't involve any I/O operation, it is possible to use the Task<'T> type from the TPL. Tasks are highly optimized and have a very low overhead.
Tasks are also useful for exposing functionality implemented in F# to a C# client. For example, nonblocking operations of an agent or asynchronous workflow can be exposed to C# using the Async.StartAsTask method.
For more information about using the Task Parallel Library from F#:
Book Chapter 14: “Writing parallel functional programs” explains how to use the Task Parallel Library to write data-parallel and task-based parallel programs. This approach complements agent-based parallelism in F#.
Step 2: Encapsulating Agents into Objects explains how to use Async.StartAsTask to develop an agent that can be easily used from C#.
Parallel Programming in F#: Introducing the sample provides information about F# samples from the book Parallel Programming with Microsoft .NET.
The F# PowerPack contains the PSeq module that provides a nice wrapper for using Parallel LINQ from F#.
To download the code snippets shown in this article, go to http://code.msdn.microsoft.com/Chapter-2-Concurrent-645370c3
This article looked at the technologies available for concurrent and server-side programming in F#. The key that makes F# a great language for server-side programming is the support for asynchronous workflows. Asynchronous workflows make it possible to write code that involves asynchronous operations (such as I/O) using standard control-flow constructs for loops and exception handling. This is the key for writing scalable and reliable applications.
Server-side applications in F# also often use agents. Agents can be used for developing thread-safe components that encapsulate the state. The core technologies such as asynchronous workflows and agents can also be combined with events (for simple declarative processing) and the Task Parallel Library (for efficient parallelism).
Previous article: Step 2: Working with Functional Lists
Next article: Tutorial: Creating a Chat Server Using Mailboxes