Prerequisites

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.

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: 
open SQLite
open System
open System.IO

[<CLIMutable>]
type UserName = {Id: Guid; FirstName: string; LastName:string }

type DatabaseManager() = 

    let monitor = new Object()
    let dbName = "database.db3"
    let mutable connection: SQLiteConnection = null

    member this.Init() =
        lock monitor (fun () -> 
            connection <- new SQLiteConnection(Path.Combine(path, dbName), false) )
            connection.CreateTable<UserName>() |> ignore

    member this.GetAllUsers(): Seq<UserName> = 
        lock monitor (fun () -> 
            connection.Table<UserName>())

    //Rest of read write data

// In app startup 
let deviceDatabasePath = "" // real implementation would db path from IOC container
let databaseManager = new DatabaseManager()
databaseManager.Init(deviceDatabasePath)

As a staring 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 our 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: 
open SQLite
open System
open System.IO

[<CLIMutable>]
type UserName = {Id: Guid; FirstName: string; LastName:string }
type Message = Init of string

type DatabaseManager() = 

    let mutable connection: SQLiteConnection = null
    let dbName = "database.db3"
    let mailbox = MailboxProcessor.Start(fun inbox -> 

        let rec loop () = async {
            let! msg = inbox.Receive()
            match msg with 
            | Init path -> 
                connection <- new SQLiteConnection(Path.Combine(path, dbName), false)
                connection.CreateTable<UserName>() |> ignore

            return! loop ()
        }
        loop ())

    member this.Init(path: string) =
        mailbox.Post(Init path)    

    // rest of methods to read/write data

// In app startup 
let deviceDatabasePath = "" // real implementation would db path from IOC container
let databaseManager = new DatabaseManager()
databaseManager.Init(deviceDatabasePath)

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

lets 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: 
// Update the type to handle replying
type Message = Init of string | GetUserNames of AsyncReplyChannel<seq<UserName>>

    // update mailbox to handle the message and reply with the data
    match msg with 
    | Init path -> 
        connection <- new SQLiteConnection(Path.Combine(path, dbName), false)
        connection.CreateTable<UserName>() |> ignore
    | GetUserNames replyChannel -> 
        replyChannel.Reply(connection.Table<UserName>()) 

// Add a method on the class to use the mailbox and reply synchronously with the data
member this.GetAllUsers () =
    mailbox.PostAndReply GetUserNames 

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: 
open SQLite
open System
open System.IO

[<CLIMutable>]
type UserName = {Id: Guid; FirstName: string; LastName:string }
type Message = Init of string | GetUserNames of AsyncReplyChannel<seq<UserName>>

type DatabaseManager() = 

    let mutable connection: SQLiteConnection = null
    let dbName = "database.db3"
    let mailbox = MailboxProcessor.Start(fun inbox -> 

        let rec loop () = async {
            let! msg = inbox.Receive()
            match msg with 
            | Init path -> 
                connection <- new SQLiteConnection(Path.Combine(path, dbName), false)
                connection.CreateTable<UserName>() |> ignore
            | GetUserNames replyChannel -> 
                replyChannel.Reply(connection.Table<UserName>()) 
            
            return! loop ()
        }
        loop ())

    member this.Init(path: string) =
        mailbox.Post(Init path)   

    member this.GetAllUsers () =
        mailbox.PostAndReply GetUserNames 

    // rest of methods to read/write data

// In app startup 
let deviceDatabasePath = "" // real implementation would db path from IOC container
let databaseManger = new DatabaseManager()
databaseManger.Init(deviceDatabasePath)
let usernames = databaseManger.GetAllUsers()

Doing things asynchronously

For the next variation we have replying asynchronously. To change the synchronously example to asynchronously, only one method needs to changed:

1: 
2: 
3: 
4: 
    member this.GetAllUsers (): Async<seq<UserName>> =
        mailbox.PostAndAsyncReply GetUserNames 

    let usernames = databaseManger.GetAllUsers() |> Async.RunSynchronously

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 to our SQLiteConnection problem. First off let’s update our Message to have the values we need.

1: 
type Message = Init of string | GetUserNames | Create of UserName

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 anther 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: 
    // Declared at the top, in DatabaseManager
    let event = new Event<seq<UserName>>()
    let context = 
        match SynchronizationContext.Current with 
        | null -> new SynchronizationContext()
        | ctx -> ctx

    let raiseEvent args = 
        context.Post((fun _ -> event.Trigger args), state=null)

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: 
// Inside the mailbox
match msg with 
| Init path -> 
    connection <- new SQLiteConnection(Path.Combine(path, dbName), false)
    connection.CreateTable<UserName>() |> ignore
| GetUserNames -> 
    connection.Table<UserName>() |> raiseEvent
| Create userName -> 
    connection.Insert(userName) |> ignore

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: 
// In DatabaseManager
member this.Create(firstName: string, lastName: string) =
    mailbox.Post <| Create {Id = Guid.NewGuid(); FirstName = firstName; LastName = lastName}

member this.ReceiveAllUsers = 
    event.Publish

member this.RequestAllUsers () =
    mailbox.Post GetUserNames 

Adding data is a single method that takes in the required. It simple makes a Post call to 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 clint will need to subscribe to the event. ReceiveAllUsers allows the clint 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 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: 
let deviceDatabasePath = "/Users/sam.williams/Desktop/" // real implementation would be db path from IOC container
let databaseManger = new DatabaseManager()
databaseManger.Init(deviceDatabasePath)
databaseManger.Create("Sam", "Williams")
databaseManger.ReceiveAllUsers.Add(fun users -> users |> Seq.iter (printfn "%A"))
databaseManger.RequestAllUsers()

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 the 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: 
#r "../packages/sqlite-net-pcl/lib/portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10/SQLite-net.dll" 
#r "../packages/SQLitePCLRaw.core/lib/Xamarin.Mac20/SQLitePCLRaw.core.dll"
#r "../packages/SQLitePCLRaw.provider.e_sqlite3.macos/lib/Xamarin.Mac20/SQLitePCLRaw.provider.e_sqlite3.dll"
#r "../packages/SQLitePCLRaw.bundle_green/lib/Xamarin.Mac20/SQLitePCLRaw.batteries_v2.dll"
#r "../packages/SQLitePCLRaw.provider.sqlite3.ios_unified/lib/Xamarin.iOS10/SQLitePCLRaw.provider.sqlite3.dll"
#r "../packages/SQLitePCLRaw.provider.e_sqlite3.net45/lib/net45/SQLitePCLRaw.provider.e_sqlite3.dll"

open SQLite
open System
open System.IO
open System.Threading

[<CLIMutable>]
type UserName = {Id: Guid; FirstName: string; LastName:string }
type Message = Init of string | GetUserNames | Create of UserName

type DatabaseManager() = 

    let event = new Event<seq<UserName>>()
    let mainThread = 
        match SynchronizationContext.Current with 
        | null -> new SynchronizationContext()
        | ctx -> ctx

    let raiseEvent args = 
        mainThread.Post((fun _ -> event.Trigger args), state=null)

    let mutable connection: SQLiteConnection = null
    let dbName = "database.db3"
    let mailbox = MailboxProcessor.Start(fun inbox -> 
    
        // Helper to get out any exceptions that may occur
        let rec getException indent (e:exn) = 
            printfn "Finding exception message"
            if e.InnerException = null then 
                sprintf "%s\n%s" e.Message e.StackTrace
            else 
                sprintf "%s%s\n%s" indent (getException (sprintf "%s\t" indent) e.InnerException) e.StackTrace 

        // generalised catch for any exceptions on each branch
        let catch f = 
            try 
                f () 
            with 
            | e -> printfn "Error: %s\n%s" e.Message (getException "" e)
            
        let rec loop () = async {
            let! msg = inbox.Receive()
            match msg with 
            | Init path -> 
                catch <| fun () -> 
                    connection <- new SQLiteConnection(Path.Combine(path, dbName), false)
                    connection.CreateTable<UserName>() |> ignore
                    printfn "Connection created!"
            | GetUserNames -> 
                catch <| fun () -> 
                    connection.Table<UserName>() |> raiseEvent
            | Create userName -> 
                catch <| fun () -> 
                    connection.Insert(userName) |> ignore

            return! loop ()
        }
        loop ())

    member this.Init(path: string) =
        mailbox.Post(Init path)   

    member this.Create(firstName: string, lastName: string) =
        mailbox.Post <| Create {Id = Guid.NewGuid(); FirstName = firstName; LastName = lastName}

    member this.RequestAllUsers () =
        mailbox.Post GetUserNames 

    member this.ReceiveAllUsers = 
        event.Publish

    // rest of methods to read/write data

// In app startup 
let deviceDatabasePath = "/Users/sam.williams/Desktop/" // real implementation would db path from IOC container
let databaseManger = new DatabaseManager()
databaseManger.Init(deviceDatabasePath)
databaseManger.Create("Sam", "Williams")
databaseManger.ReceiveAllUsers.Add(fun users -> users |> Seq.iter (printfn "%A"))
databaseManger.RequestAllUsers()
namespace System
namespace System.IO
Multiple items
type CLIMutableAttribute =
  inherit Attribute
  new : unit -> CLIMutableAttribute

Full name: Microsoft.FSharp.Core.CLIMutableAttribute

——————–
new : unit -> CLIMutableAttribute

type UserName =
  {Id: Guid;
   FirstName: string;
   LastName: string;}

Full name: Mailbox.UserName

UserName.Id: Guid
Multiple items
type Guid =
  struct
    new : b:byte[] -> Guid + 4 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : o:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member ToByteArray : unit -> byte[]
    member ToString : unit -> string + 2 overloads
    static val Empty : Guid
    static member NewGuid : unit -> Guid
    static member Parse : input:string -> Guid
    static member ParseExact : input:string * format:string -> Guid
    …
  end

Full name: System.Guid

——————–
Guid ()
Guid(b: byte []) : unit
Guid(g: string) : unit
Guid(a: int, b: int16, c: int16, d: byte []) : unit
Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit

UserName.FirstName: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

——————–
type string = String

Full name: Microsoft.FSharp.Core.string

UserName.LastName: string
Multiple items
type DatabaseManager =
  new : unit -> DatabaseManager
  member GetAllUsers : unit -> 'a
  member Init : unit -> unit

Full name: Mailbox.DatabaseManager

——————–
new : unit -> DatabaseManager

val monitor : Object
Multiple items
type Object =
  new : unit -> obj
  member Equals : obj:obj -> bool
  member GetHashCode : unit -> int
  member GetType : unit -> Type
  member ToString : unit -> string
  static member Equals : objA:obj * objB:obj -> bool
  static member ReferenceEquals : objA:obj * objB:obj -> bool

Full name: System.Object

——————–
Object() : unit

val dbName : string
val mutable connection : obj
val this : DatabaseManager
val lock : lockObject:'Lock -> action:(unit -> 'T) -> 'T (requires reference type)

Full name: Microsoft.FSharp.Core.Operators.lock

type Path =
  static val InvalidPathChars : char[]
  static val AltDirectorySeparatorChar : char
  static val DirectorySeparatorChar : char
  static val PathSeparator : char
  static val VolumeSeparatorChar : char
  static member ChangeExtension : path:string * extension:string -> string
  static member Combine : [<ParamArray>] paths:string[] -> string + 3 overloads
  static member GetDirectoryName : path:string -> string
  static member GetExtension : path:string -> string
  static member GetFileName : path:string -> string
  …

Full name: System.IO.Path

Path.Combine([<ParamArray>] paths: string []) : string
Path.Combine(path1: string, path2: string) : string
Path.Combine(path1: string, path2: string, path3: string) : string
Path.Combine(path1: string, path2: string, path3: string, path4: string) : string
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore

module Seq

from Microsoft.FSharp.Collections

val deviceDatabasePath : string

Full name: Mailbox.deviceDatabasePath

val databaseManager : DatabaseManager

Full name: Mailbox.databaseManager

member DatabaseManager.Init : unit -> unit
type Message = | Init of string

Full name: Mailbox.Message

union case Message.Init: string -> Message
Multiple items
type MailboxProcessor<'Msg> =
  interface IDisposable
  new : body:(MailboxProcessor<'Msg> -> Async<unit>) * ?cancellationToken:CancellationToken -> MailboxProcessor<'Msg>
  member Post : message:'Msg -> unit
  member PostAndAsyncReply : buildMessage:(AsyncReplyChannel<'Reply> -> 'Msg) * ?timeout:int -> Async<'Reply>
  member PostAndReply : buildMessage:(AsyncReplyChannel<'Reply> -> 'Msg) * ?timeout:int -> 'Reply
  member PostAndTryAsyncReply : buildMessage:(AsyncReplyChannel<'Reply> -> 'Msg) * ?timeout:int -> Async<'Reply option>
  member Receive : ?timeout:int -> Async<'Msg>
  member Scan : scanner:('Msg -> Async<'T> option) * ?timeout:int -> Async<'T>
  member Start : unit -> unit
  member TryPostAndReply : buildMessage:(AsyncReplyChannel<'Reply> -> 'Msg) * ?timeout:int -> 'Reply option
  …

Full name: Microsoft.FSharp.Control.MailboxProcessor<_>

——————–
new : body:(MailboxProcessor<'Msg> -> Async<unit>) * ?cancellationToken:Threading.CancellationToken -> MailboxProcessor<'Msg>

static member MailboxProcessor.Start : body:(MailboxProcessor<'Msg> -> Async<unit>) * ?cancellationToken:Threading.CancellationToken -> MailboxProcessor<'Msg>
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async

type AsyncReplyChannel<'Reply> =
  member Reply : value:'Reply -> unit

Full name: Microsoft.FSharp.Control.AsyncReplyChannel<_>

Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

——————–
type seq<'T> = Collections.Generic.IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>

Multiple items
type Async =
  static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
  static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
  static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
  static member AwaitTask : task:Task -> Async<unit>
  static member AwaitTask : task:Task<'T> -> Async<'T>
  static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
  static member CancelDefaultToken : unit -> unit
  static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
  static member Choice : computations:seq<Async<'T option>> -> Async<'T option>
  static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
  …

Full name: Microsoft.FSharp.Control.Async

——————–
type Async<'T> =

Full name: Microsoft.FSharp.Control.Async<_>

static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:Threading.CancellationToken -> 'T
Multiple items
module Event

from Microsoft.FSharp.Control

——————–
type Event<'T> =
  new : unit -> Event<'T>
  member Trigger : arg:'T -> unit
  member Publish : IEvent<'T>

Full name: Microsoft.FSharp.Control.Event<_>

——————–
type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate)> =
  new : unit -> Event<'Delegate,'Args>
  member Trigger : sender:obj * args:'Args -> unit
  member Publish : IEvent<'Delegate,'Args>

Full name: Microsoft.FSharp.Control.Event<_,_>

——————–
new : unit -> Event<'T>

——————–
new : unit -> Event<'Delegate,'Args>

Guid.NewGuid() : Guid
val iter : action:('T -> unit) -> source:seq<'T> -> unit

Full name: Microsoft.FSharp.Collections.Seq.iter

val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn

namespace System.Threading
type exn = Exception

Full name: Microsoft.FSharp.Core.exn

val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf

Leave a Reply

Your email address will not be published. Required fields are marked *