【问题标题】:Simultaneous OleDbDataAdapter.Fill Calls on Separate Threads?在单独的线程上同时调用 OleDbDataAdapter.Fill?
【发布时间】:2014-04-25 05:33:56
【问题描述】:

这里是第一个计时器,所以请放轻松。理论上是否可以同时在不同的线程上执行两个 OleDBDataAdapter.Fill 调用 - 还是从根本上存在缺陷?

考虑一个包含 2 个按钮和 2 个数据网格视图的表单。每个按钮单击都会使用 Async \ Await \ Task.Run 模式启动一个工作线程,该模式调用一个方法以返回填充的数据表并将其分配给其中一个数据网格视图。第一个线程中的 .Fill 需要 30 秒才能完成。第二个线程中的 .Fill 需要 1 秒才能完成。单独启动时,两个按钮都按预期工作。

但是,如果我启动第一个工作线程(30 秒填充),然后启动第二个线程(1 秒填充),则在第一个 .Fill 调用完成之前不会填充第二个 DataGridView。我预计第二个 datagridview 将在 1 秒内填充,而第一个 datagridview 将在大约 30 秒后填充。

我在我的示例代码中使用 OleDBDataAdapter 和 SqlDataAdapter 复制了这个问题。如果我用简单的 Thread.Sleep(30000) 替换长时间运行的查询,第二个 datagridview 会立即填充。这让我相信这不是我的设计模式的问题,而是特定于同时发出 .Fill 调用的问题。

Private Async Sub UltraButton1_Click(sender As Object, e As EventArgs) Handles UltraButton1.Click

    Dim Args As New GetDataArguments
    Args.ConnectionString = "some connection string"
    Args.Query = "SELECT LongRunningQuery from Table"

    Dim DT As DataTable = Await Task.Run(Function() FillDataTable(Args))
    If DataGridView1.DataSource Is Nothing Then
        DataGridView1.DataSource = DT
    Else
        CType(DataGridView1.DataSource, DataTable).Merge(DT)
    End If

End Sub

Function FillDataTable(Args As GetDataArguments) As DataTable

    Dim DS As New DataTable

    Using Connection As New OleDbConnection(Args.ConnectionString)
        Using DBCommand As New OleDbCommand(Args.Query, Connection)
            Using DataAdapter As New OleDbDataAdapter(DBCommand)
                DataAdapter.Fill(DS)
            End Using
        End Using
    End Using

    Return DS

End Function

Private Async Sub UltraButton2_Click(sender As Object, e As EventArgs) Handles UltraButton2.Click

    Dim DS As DataTable = Await Task.Run(Function() LoadSecondDGV("1234"))
    DataGridView2.DataSource = DS

End Sub

Function LoadSecondDGV(pnum As String) As DataTable

    Dim DX As New DataTable

    Using xConn As New OleDbConnection("some connection string")
        Using DataAdapter As New OleDbDataAdapter("Select name from products where PNUM = """ & pnum & """", xConn)
            DataAdapter.Fill(DX)
        End Using
    End Using

    Return DX

End Function

【问题讨论】:

    标签: vb.net multithreading oledbdataadapter


    【解决方案1】:

    这取决于数据源是什么。某些数据源(如 Excel)一次只允许一个连接。其他数据源(如 Access)将允许多个连接,但实际上以串行方式完成结果,这样您就不会获得任何东西。其他数据源,如 Sql Server,将允许您正在寻找真正的并行活动。

    在这种情况下,您提到您还尝试使用 SqlDataAdapter,这向我表明您正在与 Sql Server 对话,这应该是可能的。这里可能发生的是,您的第一个查询锁定第二个查询所需的一些数据。您可以通过更改transaction isolation level 或仔细使用with (nolock) 提示(强烈推荐前一个选项)来解决此问题。

    要记住的另一件事是,仅当您为每个查询使用单独的连接,或者您专门启用了多个活动结果集功能时,这才有效。看起来你在这里使用了单独的连接对象,所以你应该没问题,但我认为这仍然是值得提出的。

    最后,我需要评论一下您的FillDataTable() 方法。此方法要求您提供完整的 Sql 字符串,这实际上迫使您编写极易受到 sql 注入攻击的代码。继续使用所示的方法实际上可以保证您的应用程序将被黑客入侵,可能迟早会被黑客入侵。您需要修改此方法,以鼓励您使用参数化查询。

    【讨论】:

    • 我实际上使用的是 Access 数据库,但我确实以完全相同的行为模拟了针对 SQL Server 的测试。在这两种情况下,我都查询了完全不相关的表,以确保我没有阻止自己。我很欣赏 sql 注入警告 - 这只是一些快速而肮脏的 PoC winforms 代码。还有其他想法吗?我意识到考虑到连接字符串等的性质,我没有准备好执行的代码,但我很想知道是否有人可以重现这个问题。
    • 实际上,如果我在每个线程中查询两个完全独立的不相关的 Access 数据库,问题仍然存在。这一定意味着 DataAdapter 对象的固有特性正在跨线程共享,不是吗?
    • @rSquared 我刚刚意识到您正在使用async 来执行此操作。了解async 是否 使用多个线程很重要。它允许事情在在同一个线程中乱序运行。它提供了并行性的外观,并且经常(much)更有效地使用您的cpu 或单线程,但它实际上并不是并行的。如果你有一些阻塞的东西,比如数据库调用,你仍然会看到一个调用完成,然后另一个。
    • 我承认这是我第一次使用 Async 和 Task.Run,​​但我看到线程窗口中产生了多个工作线程。此外,如果我在 FillDataTable 和 LoadSecondDGV 例程中都使用 TracePoints 功能 - 它们被标识为在单独的线程上运行。我误解了我在看什么吗?
    猜你喜欢
    • 2021-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-09
    • 2012-09-04
    • 2017-12-19
    相关资源
    最近更新 更多