6

I'm building a .NET 4.0 application that uses ADO.NET, so I cannot use async/await. I don't want a solution for that, but I do want to know what of the following implementations is best and why. My unit tests pass for all three implementations, but I want to know the difference between these three.

#1 Nesting tasks

In my first implementation I wrap a task in another task. I think spinning up two tasks is bad for performance, but I'm not sure.

public virtual Task<IDataReader> ExecuteReaderAsync(IDbCommand dbCommand, CancellationToken cancellationToken)
{
    return Task.Factory.StartNew(() =>
    {
        var sqlCommand = CheckIfSqlCommand(dbCommand);
        PrepareExecuteReader(dbCommand);

        return Task<IDataReader>
            .Factory
            .FromAsync(sqlCommand.BeginExecuteReader, sqlCommand.EndExecuteReader, null)
            .Result;
    }, cancellationToken);
}

#2 Using TaskCompletionSource

Then I tried wrapping the result in a TaskCompletionSource so I just have one task.

public virtual Task<IDataReader> ExecuteReaderAsync(IDbCommand dbCommand, CancellationToken cancellationToken)
{
    var taskCompletionSource = new TaskCompletionSource<IDataReader>();
    var sqlCommand = CheckIfSqlCommand(dbCommand);
    PrepareExecuteReader(dbCommand);

    var reader = Task<IDataReader>
        .Factory
        .FromAsync(sqlCommand.BeginExecuteReader, sqlCommand.EndExecuteReader, null)
        .Result;

    taskCompletionSource.SetResult(reader);

    return taskCompletionSource.Task;
}

#3 returning Task directly

My final solution is to directly return the task I created instead of wrapping it.

public virtual Task<IDataReader> ExecuteReaderAsync(IDbCommand dbCommand, CancellationToken cancellationToken)
{
    var sqlCommand = CheckIfSqlCommand(dbCommand);
    PrepareExecuteReader(dbCommand);

    return Task<IDataReader>
        .Factory
        .FromAsync(sqlCommand.BeginExecuteReader, sqlCommand.EndExecuteReader, null);
}

So basically my question is:

What option should I use or is there a better way to do this?

MichielDeRouter
  • 426
  • 4
  • 21
annemartijn
  • 1,538
  • 1
  • 23
  • 45
  • 2
    Targeting .Net 4.0 doesn't necessarily mean you can't use async-await. You can use it if you add Microsoft.Bcl.Async to your project, though it requires VS 2012. – svick Feb 27 '14 at 16:56
  • Developers should be able to use vs2010 also, but I appreciate your feedback. – annemartijn Feb 27 '14 at 20:13

2 Answers2

4

Your #3 is the best. The first two introduce complication for no reason.

1 potentially adds another thread purely to run CheckIfSqlCommand() and PrepareExecuteReader() asynchronously. This may be what you wanted, but they don't sound like commands that are going to take a long time.

2 references .Result of the task, which will block until the task is complete, so defeats the whole purpose of using tasks.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • I wonder if there are situations where you don't use tasks for I/O or networking. If there are, how do I know when to use asynchronicity? – annemartijn Feb 27 '14 at 10:03
  • @annemartijn if you have to ask, don't use async. Use it when you must. – usr Feb 27 '14 at 13:57
-1

There are two scenes we use asynchronous programming with Tasks, one is massive computing, another is I/O.
In massive computing situation, we always use Task.Run to ask a thread from thread pool to avoid blocking thread.
In I/O situation, if async api is not provided, we always use TaskCompletionSource or Task.Factory.FromAsync to build an async method. I think mix these two is not a good solution.
By the way, Task.Run is always been used in client application, server end generally not used Task.Run due to concurrent request.
Here is a good post you can refer to:
https://learn.microsoft.com/en-us/archive/msdn-magazine/2010/september/async-tasks-simplify-asynchronous-programming-with-tasks

Giulio Caccin
  • 2,962
  • 6
  • 36
  • 57