Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.ContinueWith confusion

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:

  • ContinueWith consuming extra threadpool resources - Readerfactory is very light!
  • Task.Result blocking

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?

like image 222
n8wrl Avatar asked Nov 30 '12 18:11

n8wrl


2 Answers

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.

like image 131
Stephen Cleary Avatar answered Nov 23 '22 13:11

Stephen Cleary


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.

like image 30
usr Avatar answered Nov 23 '22 12:11

usr