With ASP.NET 4.5 I'm trying to play with the new async/await toys. I have a IDataReader-implementing class that wraps a vendor-specific reader (like SqlDatareader). I have a simple ExecuteSql() method that operates synchronously like so:
public IDataReader ReaderForSql(string sql)
{
var cmd = NewCommand(sql, CommandType.Text);
return DBReader.ReaderFactory(cmd.ExecuteReader());
}
What I want is an Async version of this. Here's my first try:
public Task<IDataReader> ReaderForSqlAsync(string sql, CancellationToken ct)
{
var cmd = NewCommand(sql, CommandType.Text);
return cmd.ExecuteReaderAsync(ct)
.ContinueWith(t => DBReader.ReaderFactory(t.Result));
}
and I use it:
using (var r = await connection.ReaderForSqlAsync("SELECT ...", cancellationToken))
{
...
}
This works great in my limited testing so far. But after watching this Cloud9 video a few times: http://channel9.msdn.com/Events/aspConf/aspConf/Async-in-ASP-NET I got worreid about warnings they gave regarding:
and since I am passing a ContinuationToken to ExecuteReaderAsync() it seems cancellation is just yet another reason ExecuteReaderAsync() could fail (it's SQL after all!)
What will be the state of the task when I try to ContinueWith it? Will t.Result block? throw? do the wrong thing?
ContinueWith
will use the current task scheduler (a thread pool thread) by default, but you can change that by passing TaskContinuationOptions.ExecuteSynchronously
and an explicit TaskScheduler
.
That said, I would make this as a first effort:
public async Task<IDataReader> ReaderForSqlAsync(string sql, CancellationToken ct)
{
var cmd = NewCommand(sql, CommandType.Text);
var readerResult = await cmd.ExecuteReaderAsync(ct).ConfigureAwait(false);
return DBReader.ReaderFactory(readerResult);
}
async
and await
handle all the ContinueWith
delicacies and edge conditions in a consistent manner for you. It may be possible to complicate this code to be faster if performance testing indicates it's a serious problem.
Result
blocks if the task has not completed. But in the continuation handler it already has. So it does not block. You are doing the right thing.
When you invoke Result
on a faulted task (and you say this might happen) the exception is rethrown. This causes your continuation to fault with the same exception which causes the final task returned from ReaderForSqlAsync
to also be faulted. This is a good thing: The entire chain of tasks faulted and all exceptions have been observed (on contrast to being swallowed). So this is best-practice, too.
Using a thread for compute-bound work is always ok. So again, you are doing the right thing using ContinueWith. You have to compute the IDataReader
somewhere after all. You cannot not compute it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With