We hear a lot about concurrency nowadays, how important it is, and how it is “the next major revolution in how we write software”.
So what do we actually mean by “concurrency” and how can F# help?
The simplest definition of concurrency is just “several things happening at once, and maybe interacting with each other”. It seems a trivial definition, but the key point is that most computer programs (and languages) are designed to work serially, on one thing at a time, and are not well-equipped to handle concurrency.
And even if computer programs are written to handle concurrency, there is an even more serious problem: our brains do not do well when thinking about concurrency. It is commonly acknowledged that writing code that handles concurrency is extremely hard. Or I should say, writing concurrent code that is correct is extremely hard! It’s very easy to write concurrent code that is buggy; there might be race conditions, or operations might not be atomic, or tasks might be starved or blocked unnecessarily, and these issues are hard to find by looking at the code or using a debugger.
Before talking about the specifics of F#, let’s try to classify some of the common types of concurrency scenarios that we have to deal with as developers:
Of course, these are vague definitions and overlap in practice. In general, though, for all these cases, the actual implementations that address these scenarios tend to use two distinct approaches:
F# offers a number of different approaches to writing concurrent code:
For multitasking and asynchronous problems, F# can directly use all the usual .NET suspects, such as
IAsyncResult. But it also offers a much simpler model for all types of async IO and background task management, called “asynchronous workflows”.
We will look at these in the next post.
An alternative approach for asynchronous problems is to use message queues and the “actor model” (this is the “buffered asynchronous” design mentioned above). F# has a built in implementation of the actor model called
I am a big proponent of designing with actors and message queues, as it decouples the various components and allows you to think serially about each one.
For true CPU parallelism, F# has convenient library code that builds on the asynchronous workflows mentioned above, and it can also use the .NET Task Parallel Library.
Finally, the functional approach to event handling and reactive programming is quite different from the traditional approach. The functional approach treats events as “streams” which can be filtered, split, and combined in much the the same way that LINQ handles collections. F# has built in support for this model, as well as for the standard event-driven model.