(this page is part of the Rezoom.SQL tutorial)
Asynchronous programming
So far we have covered only synchronous invocation of SQL commands. That is, in
the following code example, command.Execute(context)
blocks the calling thread
until the results are returned from the database:
open Rezoom.SQL
open Rezoom.SQL.Synchronous
type MyQuery = SQL<"select * from MyTable">
let runMyQuery () : IReadOnlyList<MyQuery.Row> =
use context = new ConnectionContext()
let command = ListCommentsByUser.Command(name)
command.Execute(context)
This is easy to understand, and perfectly adequate (even ideal) for many applications.
However, it's a little wasteful. After all, the code is only responsible for sending the query to your SQL server and processing the results when they arrive. In between, there's no useful work being done here -- it's just waiting around.
If you're writing a web application which you expect to get a lot of traffic, you may want to free that thread up to work on other things while your database is running the query.
In modern .NET (since 4.5) this is most commonly done with the
System.Threading.Tasks.Task<'a>
type, which represents asynchronous work that
will eventually result in a value of type 'a
.
You can run a SQL command as a Task
using the extension methods from
Rezoom.SQL.Asynchronous
, like so:
open Rezoom.SQL
open Rezoom.SQL.Asynchronous // <-- notice the namespace difference
type MyQuery = SQL<"select * from MyTable">
// v-- notice the type difference
let runMyQuery () : Task<IReadOnlyList<MyQuery.Row>> =
use context = new ConnectionContext()
let command = ListCommentsByUser.Command(name)
command.Execute(context)
The task computation expression
Note that when you have a value of type Task<'a>
and want to do something with
its result (of type 'a
), you should not use the .Result
property. This
will block the calling thread while waiting for the task to finish, defeating
the entire point of using tasks in the first place!
Instead, you can use the task computation expression that comes with Rezoom, like so:
open Rezoom.SQL
open Rezoom.SQL.Asynchronous
type MyQuery = SQL<"select * from MyTable">
let runMyQuery () : Task<int> =
task {
use context = new ConnectionContext()
let command = ListCommentsByUser.Command(name)
// notice that this variable is bound with `let!`
let! results = command.Execute(context)
return results.[0].Id
}
Interop with F# async
F# comes with another type somewhat similar to Task<'a>
for representing
asynchronous computations. It is called Async<'a>
.
Rezoom does not come with wrappers for this, but you can convert between the two
types using Async.AwaitTask
and Async.StartAsTask
.
Consider using Rezoom
If you're already going to go to the trouble of writing asynchronous code, it's
worth checking out the Rezoom tutorial. Rezoom's Plan
type is a bit like Async
or Task
, but is smart about data dependencies. It
can save you from having to write complex or tightly coupled code to avoid
excessive database round trips.