Optimising your DAL with async ADO.NET
Andrew Bullock @trullock
Overview
-
Problems with Sync
-
Sync vs Async Syntax
-
(Non)Sequential Access
-
TDS Protocol
-
Best Practices / ProTips
public async Task CallAsyncThing() {
// 1. We enter this method
// 2. We call this method
var task = DoThingAsync();
// 5. We are here, without
// waiting 1000ms
// We wait (non blocking) for the
// task to complete (it may already have)
await task;
}
public Task DoThingAsync() {
// 3. We enter this method
// 4. We return a task which will complete
// in 1000ms
return Task.Delay(1000);
}
Prerequisite
Async + Await
public void CallSyncThing() {
// 1. We enter this method
// 2. We call this method
DoThingSync();
// 4. We are here after
// DoThingSync() has finished
}
public void DoThingSync() {
// 3. We block the thread for 1000ms
Thread.Sleep(1000);
}
Problems with Sync
-
Lazy
-
Inefficient
-
Blocking
-
Less Cool than Async
if(reader.Read())
{
var user = new User
{
Id = reader.GetGuid(0)
};
var photo = reader.GetBinary(3); // Massive field
user.Age = reader.GetInt32(2);
user.Name = reader.IsDBNull(1) ? null : reader.GetString(1);
user.Photo = photo;
return user;
}
Problems with Sync
-
Inefficient + Lazy
Problems with Sync
-
Thread Blocking
Problems with Sync
-
Less Cool than Async
Sync
Async
using (var connection = new SqlConnection("..."))
{
connection.Open();
var command = connection.CreateCommand();
command.CommandText = @"
select
Id
, Name
, Age
from User
";
using (var reader = command.ExecuteReader())
{
if(reader.Read())
{
return new
{
Id = reader.GetGuid(0),
Name = reader.IsDBNull(1) ? null : reader.GetString(1),
Age = reader.GetInt32(2)
};
}
}
}
Synchronous ADO.NET Syntax
using (var connection = new SqlConnection("..."))
{
await connection.OpenAsync();
var command = connection.CreateCommand();
command.CommandText = @"
select
Id
, Name
, Age
from User
";
using (var reader = command.ExecuteReader())
{
if(await reader.ReadAsync())
{
return new
{
Id = reader.GetGuid(0),
MassiveBlob = reader.IsDBNull(1) ? null : reader.GetString(1),
Age = reader.GetInt32(2)
};
}
}
}
Asynchronous ADO.NET Syntax
using (var reader = command.ExecuteReader())
{
while (await reader.ReadAsync())
{
var user = new
{
Id = await reader.GetFieldValueAsync<Guid>(0),
Name = await reader.IsDBNullAsync(1) ? null :
await reader.GetFieldValueAsync<string>(1),
Age = await reader.GetFieldValueAsync<int>(2)
};
}
}
Asynchronous ADO.NET
Sequential CommandBehaviour
TDS Protocol
When to use what
Always NextResultAsync()
NextResut() vs NextResultAsync()
When to use what
Read() vs ReadAsync()
Probably ReadAsync()
When to use what
IsDBNull and GetFieldValue<T> vs IsDBNullAsync and GetFieldValueAsync<T>
Pro Tips:
- Avoid: NTEXT, TEXT, IMAGE, TVP, UDT, XML, [N]VARCHAR(MAX), VARBINARY(MAX) where possible
- Keep the maximum size of variable column as small as possible
- Use NULL when you have no data
- Understand your packet vs row size
- Select large columns last
- Stream large data
var outputStream = new MemoryStream();
using (var reader = command.ExecuteReader(CommandBehavior.SequentialAccess))
{
while (await reader.ReadAsync())
{
var stream = reader.GetStream(0);
await stream.CopyToAsync(outputStream);
}
}
Asynchronous ADO.NET
Test It Yourself
- Set a low connection pool size:
"data source=your_server; initial catalog=your_db; max pool size=1"
- Set a low thread pool size:
<configuration>
-
<system.web> <applicationPool maxConcurrentRequestsPerCPU="1" maxConcurrentThreadsPerCPU="1" /> </system.web> </configuration>
Resources
- https://dpaoliello.wordpress.com/2014/04/22/improving-async-performance-for-sqldatareader/
- http://blogs.msdn.com/b/adonet/archive/2012/04/20/using-sqldatareader-s-new-async-methods-in-net-4-5-beta.aspx
- http://blogs.msdn.com/b/adonet/archive/2012/07/15/using-sqldatareader-s-new-async-methods-in-net-4-5-beta-part-2-examples.aspx
Thanks
Andrew Bullock @trullock
myunidays.com/careers
async ado.net
By trullock
async ado.net
- 1,142