SqlDataAdapter.Fill - 异步方法

Posted

技术标签:

【中文标题】SqlDataAdapter.Fill - 异步方法【英文标题】:SqlDataAdapter.Fill - Asynchronous approach 【发布时间】:2010-01-21 11:50:06 【问题描述】:

使用 C#/.NET 3.5。

目前我正在使用 SqlDataAdapter.Fill() 一个接一个地填充 2 个数据表。

我想并行填充这两个数据表,同时通过异步执行每个数据表。但是,Fill() 方法没有异步版本 - 即 BeginFill() 会很棒!

我尝试过的一种方法是(伪):

    SqlCommand1.BeginExecuteReader // 第一个查询,用于 DataTable1 SqlCommand2.BeginExecuteReader // 第二次查询,用于 DataTable2 SqlCommand1.EndExecuteReader SqlCommand2.EndExecuteReader DataTable1.Load(DataReader1) DataTable2.Load(DataReader2)

但是,DataTable.Load() 需要很长时间: 完成第 1 步到第 4 步需要 3 秒。 然后步骤 5 需要 22 秒。 第 6 步需要 17 秒。 因此,第 5 步和第 6 步总共需要 39 秒。

最终结果是,与一个接一个地执行 2 个 SqlDataAdapter.Fills 相比,这对我没有任何好处。我希望最终结果是整个过程只需要最长的查询(或尽可能接近)。

寻找推荐的方法来最终获得真正异步的方法来填充 DataTable。

还是我自己管理它并滚动 2 个单独的线程,每个线程填充一个 DataTable?

【问题讨论】:

【参考方案1】:

我建议为每个单独的工作线程。你可以使用ThreadPool.QueueUserWorkItem。

List<AutoResetEvent> events = new List<AutoResetEvent>();

AutoResetEvent loadTable1 = new AutoResetEvent(false);
events.Add(loadTable1);
ThreadPool.QueueUserWorkItem(delegate 
 
     SqlCommand1.BeginExecuteReader;
     SqlCommand1.EndExecuteReader;
     DataTable1.Load(DataReader1);
     loadTable1.Set();
);

AutoResetEvent loadTable2 = new AutoResetEvent(false);
events.Add(loadTable2);
ThreadPool.QueueUserWorkItem(delegate 
 
     SqlCommand2.BeginExecuteReader;
     SqlCommand2.EndExecuteReader;
     DataTable2.Load(DataReader2);
     loadTable2.Set();
);

// wait until both tables have loaded.
WaitHandle.WaitAll(events.ToArray());

【讨论】:

所以,一旦我把每一个都排好队,我怎么能等到两个都完成呢?我需要先填充两个表,然后才能继续处理它们 如果有帮助,我已将等待概念添加到我的答案中。 @AdaTheDev,您将使用在工作完成后触发的 AutoResetEvents(在每个单独的线程中)。请参阅@Neils 的答案,因为他已经提供了一个示例。 我已更新以向您展示(如果使用我的示例)您将如何实现这一点。 谢谢(两者)。我在 STA 线程上运行,这意味着我不能使用 WaitHandle.WaitAll(“不支持 STA 线程上的多个句柄的 WaitAll”)。有其他选择吗?【参考方案2】:

这是因为DataTable 有很多对象要创建(行、值)。您应该在不同的线程中完成适配器的执行和数据表的填充,并在继续之前同步等待每个操作完成。

以下代码是用记事本编写的,可能甚至无法编译,但希望您能理解...

// Setup state as a parameter object containing a table and adapter to use to populate that table here

void DoWork()

    List<AutoResetEvent> signals = GetNumberOfWaitHandles(2);

    var params1 = new DataWorkerParameters
        
            Command = GetCommand1();
            Table = new DataTable();
        

    var params2 = new DataWorkerParameters
        
            Command = GetCommand2();
            Table = new DataTable();
        

    ThreadPool.QueueUserWorkItem(state => 
        
            var input = (DataWorkerParameters)state;
            PopulateTable(input);
            input.AutoResetEvent.Set(); // You can use AutoResetEvent.WaitAll() in the caller to wait for all threads to complete
        ,
        params1
    );

    ThreadPool.QueueUserWorkItem(state => 
        
            var input = (DataWorkerParameters)state;
            PopulateTable(input);
            input.AutoResetEvent.Set(); // You can use AutoResetEvent.WaitAll() in the caller to wait for all threads to complete
        ,
        params2
    );

    WaitHandle.WaitAll(signals.ToArray());



void PopulateTable(DataWorkerParameters parameters)

    input.Command.ExecuteReader();
    input.Table.Load(input.Command);

【讨论】:

谢谢(两者)。我在 STA 线程上运行,这意味着我不能使用 WaitHandle.WaitAll(“不支持 STA 线程上的多个句柄的 WaitAll”)。有其他选择吗? 你可以提供一个Action 回调,它只在被调用足够多次时才执行下一段代码?不是很好,但应该可以。 为建议和帮助干杯。我有足够的工作来做我想做的事(并行运行查询并没有真正的好处,因为它们相互竞争)。为答案+1,因为它确实有帮助。

以上是关于SqlDataAdapter.Fill - 异步方法的主要内容,如果未能解决你的问题,请参考以下文章

SqlDataAdapter.Fill() 溢出异常

SqlDataAdapter.Fill(DataGridView.DataSource) 复制所有行

SqlClient.SqlDataAdapter.Fill(DataSet) 结果如果不选择查询

SqlDataAdapter.Fill 方法慢

SqlDataAdapter.Fill 方法慢

SqlDataAdapter#Fill:`SelectCommand.connection` 属性尚未初始化