异步填充DataTable?

ca9*_*3d9 4 c# .net-core

我在.NET Core 2.0应用程序中具有以下功能。

public DataTable CallDb(string connStr, string sql)
{
    var dt = new DataTable();
    var da = new SqlDataAdapter(sql, connStr);
    da.Fill(dt);
    return dt;
}
Run Code Online (Sandbox Code Playgroud)

如何将其转换为异步函数?

public async Task<DataTable> CallDb(string connStr, string sql)
{
    var dt = new DataTable();
    var da = new SqlDataAdapter(sql, connStr);
    da.Fill(dt); // No FillAsync to await?
    return dt;
}
Run Code Online (Sandbox Code Playgroud)

我需要使用,DataTable因为sql可能会返回具有不同架构的数据。还有什么更好的方式来处理动态模式?

SO *_*ood 8

SqlDataAdapter从未更新为包括方法的TPL版本。您可以这样做:

await Task.Run(() => da.Fill(dt));
Run Code Online (Sandbox Code Playgroud)

但这将创建一个无用的线程。

一个好的方法是使用类似以下的内容:

public async Task<DataTable> CallDb(string connStr, string sql)
{
    var dt = new DataTable();
    var connection = new SqlConnection(connStr);
    var reader = await connection.CreateCommand().ExecuteReaderAsync();
    dt.Load(reader);

    return dt;
}
Run Code Online (Sandbox Code Playgroud)

当然,using应该进行一些更改,例如声明。但是,这里您使用正确的方式使用异步调用。

  • 尽管此答案通过在方法中使用另一个等待来解决该问题,但当大量 IO 发生在 dt.Load 中时,它不会释放线程。这就是使用异步的最大好处所在。 (3认同)
  • @Joe true,当您在往返时间+ SQL Server 实际处理查询所花费的时间中释放线程时,会有一些收获。如果您有高延迟/需要花费大量时间来处理的复杂查询,那么该部分可能会很重要。但我想指出的是,大多数 IO 通常发生在您实际从读取器检索结果时,这仍然在“dt.Load”中同步执行。为了获得这个好处,您应该使用`SqlDataReader`提供的异步方法:`NextResultAsync`和`ReadAsync`,或者创建您自己的`dt.LoadAsync`。 (3认同)
  • @SaebAmini - 但在 SQL Server 处理查询时以及开始返回结果之前,它肯定会释放线程吗?所以有一些收获,而且可能相当重要。 (2认同)
  • github 上有一个关于向 DataAdapters API 添加异步功能的问题。https://github.com/dotnet/runtime/issues/22109 (2认同)

小智 7

尽管在这种情况下最初的调用ExecuteReaderAsync()不会阻塞,dt.Load(reader)但可能会执行相当于reader.Read()而不是的操作await reader.ReadAsync(),并且可能会在检索行时阻塞调用线程。

如果您确实需要 aDataTable与外部 API 一起使用,或者因为您事先不知道字段定义,但需要完全异步行为,那么您最好使用自己的代码来构造 a DataTable,添加所需的列,例如基于reader.GetName()和,然后使用和reader.GetFieldType()循环填充行。await reader.ReadAsync()dt.Rows.Add()