使用异步方式返回 .net 4.0 中的 DataTable

6

我有一个返回数据表的方法。我认为在使用.net 4.0时,可以使用异步逻辑并返回数据。但是这段代码返回了空的Datatable对象。你有什么想法这段代码有什么问题吗?

public DataTable GetData(string sql, string connectionName)
{
    DataTable dt = (DataTable)GetDataAsync(sql, connectionName).AsyncState;
    return dt;
}

private async Task<DataTable> GetDataAsync(string sql, string connectionName)
{
    return await TaskEx.Run(() => { return FillData(sql, connectionName); });
}

private DataTable FillData(string sql, string connectionName)
{
    SqlConnection conn = _connections.Where(w => w.ConnectionName == connectionName).Single().Connection;
    SqlDataAdapter adp = new SqlDataAdapter(sql, conn);
    DataSet ds = new DataSet();

    adp.Fill(ds);

    return ds.Tables[0];
}

你不能在.NET 4.0或C# 4中使用async/await。这是C# 5的新功能,并依赖于.NET 4.5中的类型。 - Jon Skeet
4
Jon - 由于他使用了TaskEx.Run,我猜测他正在使用异步目标包,这使得您可以针对4.0版本并使用async/await。 - James Manning
3个回答

10

首先,你无法在.NET 4或C# 4中使用async/await,它是C# 5的新功能。虽然有安装在.NET 4之上的CTP,但这些CTP中存在明显的错误-不要使用它们。你应该使用包含C# 5编译器的完整版本的.NET 4.5(所有这些都在Visual Studio 2012中)。

其次,你正在使用任务的错误属性,就像Cuong Le所示。 Result 属性是获取Task<T>结果的方法。

第三,在更改为使用Result属性后,你将会阻塞等待表格被获取-这样做是没有意义的。 这个:

public DataTable GetData(string sql, string connectionName)
{
    DataTable dt = (DataTable)GetDataAsync(sql, connectionName).Result;
    return dt;
}

......在很大程度上等同于:

public DataTable GetData(string sql, string connectionName)
{
    return FillData(sql, connectionName);
}

如果你打算开始一个任务并立即等待它完成,那么最好直接同步调用该方法。


@Malcolm:你不想使用4.5的原因是什么? - Jon Skeet
@Malcolm:我说过它是在C# 5.0中编写的,这是与.NET 4.5一起使用的。 - Jon Skeet
@Malcolm:不,你需要Visual Studio 2012。现在有一些VS2012的Express版本,而“通用”的C#版本(非Win8、非Web)将在Win8发布后的某个时候推出。 - Jon Skeet
让我们在聊天中继续这个讨论。点击此处进入聊天室 - Malcolm
@Malcolm:很抱歉,我现在无法聊天,因为我即将下火车步行。不过,我认为我已经向您提供了所有选项...... - Jon Skeet
显示剩余4条评论

8

我的源代码。

public static async Task<DataTable> GetDataTableAsync(this System.Data.Common.DbCommand command, CancellationToken cancellationToken, string tableName = null)
    {
        TaskCompletionSource<DataTable> source = new TaskCompletionSource<DataTable>();
        var resultTable = new DataTable(tableName ?? command.CommandText);
        DbDataReader dataReader = null;

        if (cancellationToken.IsCancellationRequested == true)
        {
            source.SetCanceled();

            await source.Task;
        }

        try
        {
            await command.Connection.OpenAsync();
            dataReader = await command.ExecuteReaderAsync(CommandBehavior.Default);
            resultTable.Load(dataReader);
            source.SetResult(resultTable);
        }
        catch (Exception ex)
        {
            source.SetException(ex);
        }
        finally
        {
            if (dataReader != null)
                dataReader.Close();

            command.Connection.Close();
        }

        return resultTable;
    }

不错,不过你还应该将取消令牌传递给OpenAsync和ExecuteReaderAsync方法,以便取消操作也能停止它们。 - Tom Deloford
谢谢你提供实际解决方案。你从哪里得到取消令牌来传递? - toddmo
你可以使用 using 模式在 dispose 时自动关闭所有内容。 - toddmo
这里只有读取器的创建是异步执行的。同时,数据加载是同步完成的:resultTable.Load(dataReader); 这是一个耗时的操作。 - Alexander Petrov

3
如果您想使用async代码,那么不要阻塞它。此外,请确保您正在使用Async Targeting Pack而不是Async CTP。
private async Task<DataTable> GetDataAsync(string sql, string connectionName)
{
  return await TaskEx.Run(() => { return FillData(sql, connectionName); });
}

private async GetAndProcessDataAsync()
{
  DataTable table = await GetDataAsync("my sql", "my connection name");
  ProcessData(table);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接