Prerequisites
- A simple understanding of F#
- A recap of F# Mailboxes: https://fsharpforfunandprofit.com/posts/concurrency-actor-model/
- A basic understanding of an SQLite database
This post aims to build on the discussion started in the prerequisites. Mailbox processors are a great alternative to use variables and locks. There are many blog other posts on MailboxProcessors, however, few of them mention how to retrieve the state they hold rather than just printing it to console. This post will explain the different approaches while applying this to a typical Xamarin app problem; a database connection. Understanding how to use the F# mailbox reply methods will be easy after reading this post!
Background
Before jumping in to the details, it is important to establish why this is important. State is a requirement of a program, but how that state is modelled is up to the programmer. Most main-stream programming languages (those OOP ones) model state with many variables. An alternative approach (and better in my opinion) is to eliminate most of the variables by modelling state through functions (how that is done is not the focus of this post). For those few variables that remain, a high quality can now taken to make sure everything is thread safe and protected. F# mailboxes are a nice way of doing that. As already stated, one of those variables is the database connection. With that out of the way, time for some code!
A database connection: The standard approach
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: |
|
As a starting point, this F# code is a very typical approach to creating a database connection that is thread safe. To explain the main points of the code, UserName is the DTO class that we’re using for a single table. For the connection, a synchronous connection is being used. Normally an async connection is used, but in the context of this post, an async connection offers little benefits. I’ve also skipped any form of interfaces for testing and will leave adding that in as an exercise for the reader.
Step one: A mailbox
First, let’s take out the lock and wrap the connection in a mailbox.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: |
|
Now we have a MailboxProcessor
holding our SQLiteConnection
and we also have a message type to create the connection. Because the connection is stored inside the mailbox, we know that things are thread safe. We’re not finished yet though since there is no way to get data out of the MailboxProcessor
Getting data out: The many possibilities
There are a few ways to get data out of the mailbox. They are:
- Reply synchronously
- Reply asynchronous
- use events
let’s use each approach to get a feel for what works best. Here are the code sections that need to be added/updated for replying synchronously:
Reply to me synchronously
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: |
|
Message
now includes a branch with GetUserNames
that also carries some data with it; the reply channel. The reply channel is also typed with the response data, in this case seq<UserName>
. Once the message type has been updated, the pattern match inside the mailbox will give a warning till it has been updated to handle the GetUserNames
case. The matching case is very simple. Just use the connection to pull out the data and pass it into the replyChannel’s reply method. The last section of code is added to DatabaseManager
as a public method. In here, we specify that the request should be made synchronously, ie post the message and block on the same thread for the response.
Here is the full output:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: |
|
Doing things asynchronously
For the next variation, we have to reply asynchronously. To change the synchronous example to asynchronously, only one method needs to changed:
1: 2: 3: 4: |
|
As stated, the change is very simple, just use PostAndAsyncReply
, and the response will be asynchronous. For the example invoking the method, I have called it synchronously (RunSynchronously
), but this should be avoided to get the benefits from asynchronous execution.
An event to rule them all
The final choice for getting data out of a MailboxProcessor
is by using events. Don Syme wrote a great a post on this here. Let’s apply that solution to the SQLiteConnection
. First off let’s update the Message
to have the values we need.
1:
|
|
The GetUserNames
is now used as an enum since we’ll use an event to return the data. I’ve also taken the liberty of adding another choice to the message that will allow a UserName
to be added to the database. This choice can be added to any/all of the examples if you want to test them with some data. Now we need to add the event along with a few helper methods to the DatabaseManager
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
As discussed earlier, the event is typed with our return type, in this case seq<UserName>
. We also capture a context that we can post back on, generally this will be the main thread of a GUI app. Finally, a helper raiseEvent
was added that raises the event on the captured context. Next up is to update the match block in our mailbox:
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
The first match is the same as before. GetUserNames
now loads all the data and calls the helper method we declared earlier. Create
is also very simple, it inserts the userName into the database and ignores the return value of the insert. We’re nearly done, we just need to expose the new functionality on the DatabaseManager
via some methods.
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
Adding data is a single method that takes in the required. It simply makes a Post
call to the mailbox with the Create
tag and the data. Getting the data our now requires two methods. Because we’re using events to get the data out, the client will need to subscribe to the event. ReceiveAllUsers
allows the client to do this by exposing the Publish
method on the event. Once the client has subscribed, the client can call RequestAllUsers
and the data will be received on the event subscription. RequestAllUsers
just makes a Post
call to the mailbox with the GetUserNames
choice. Here is some sample client code I used in an F# repl to test this out:
1: 2: 3: 4: 5: 6: |
|
It’s rather straight forward but I will go over it quickly. The first three lines are creating an instance and setting up the database as before. We then create a record so we can get something out. We then add our subscription to the databaseManager
event. For this trivial example, the users will be printed to the console. Finally, the request is made to get all the users. The full listing for the mailbox with events is listed at the end of the post.
To wrap-up, there are three possible methods to get state out of an F# mailbox, reply synchronously, reply asynchronous and via events. Hopefully one of these will be suitable the next time you need to protect a variable from thread bugs.
tl;dr: full fsx script example with events
Here is the full listing for the mailbox with events. I’ve included the required imports to run this as an fsx script on a Mac. For this script I’ve also included some very crude error handling, useful for getting the script running (but not production quality):
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: |
|