Should I wrap a task in another task or should I j

2019-06-21 04:36发布

问题:

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?

回答1:

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.