具有许多数据库调用的 VB.net 多线程

Posted

技术标签:

【中文标题】具有许多数据库调用的 VB.net 多线程【英文标题】:VB.net Multithreading with many database calls 【发布时间】:2015-10-20 17:24:22 【问题描述】:

我有一个 vb.net 应用程序,其中用户必须处理几百万条记录才能存储在数据库 (sql CE) 中。处理如下:

    从数据库中检索一些保存的数据来实例化一个对象。 执行一些计算。 将计算出的对象数据保存到数据库中的不同表中。

如果按顺序完成,前两项大约需要 30% 的时间,最后一项大约需要 70% 的时间。

我认为将大部分数据库写入更像是在单独的线程上进行批处理可能是有益的,希望将成本降低(在理想情况下)执行项目 1 和 2 所需的 30%。我尝试通过将已处理的对象存储在列表中来做到这一点,并且每当列表计数超过某个数量时,我会在单独的线程上调用操作,我会保存数据。每次保存数据时,我实际上是保存了主对象和几个相关子对象的数据,即,

cmd.CommandText = "INSERT INTO [MainObjectTable] (Data1, Data2, Data3) VALUES ( @Data1, @Data2, @Data3)"
cmd.ExecuteNonQuery()

cmd2.CommandText = "SELECT MAX(idx) FROM [MainObjectTable]"
MainObjectIdx = CInt(cmd2.ExecuteScalar())

'Loop over child objects and save each one to database'
cmd3.CommandText = "INSERT INTO [ChildObject] (MainObjectIdx, Data4, Data5, Data6) VALUES ( MainObjectIdx, @Data4, @Data5, @Data6 )"

for i = 0 to ChildList.Count -1
     [Code to Update parameters for the command object]
     cmd3.ExecuteNonQuery()
next

我锁定数据库以防止尝试同时保存多条记录。我必须这样做(我认为),因为我使用主对象的记录键来进一步处理子对象的数据,如上所示。

代码基本上看起来像这样:

Private sub CalcData()
    Dim objectList as new List(of ParentObject)
    For k = 0 to Calculations
       'Load data, instantiate object'
       Dim o as new ParentObject(IDs(k)) '<-- This pulls records from the sql ce database and loads the object'
       o.calcData  'calculate the new data based on loaded data'
       objectList.add(o) 'store in a list'
       If objectList.Count > 1000 then
            Batchsave(objectList)
            objectList.clear()
       End If
    Next
End Sub

Private Sub BatchSave(objList As List(of ParentObject))                                          
    mTaskList.Add(Tasks.Task.Factory.StartNew(
            Sub()
                DBLock.EnterWriteLock()
                Try
                  for j = 0 to objectList.count-1
                    [Code to update command object parameters, and save the object (and children) as discussed above where I show the sql statements]
                  next
                Finally
                  DBLock.ExitWriteLock()
                End Try
            End Sub))              
End Sub

我认为这种方案可以最大限度地提高性能,允许在后台线程上完成数据保存。我像批处理过程一样构建保存(一次用于 1000 条记录),因为我已经读过在更新许多记录时参数化 sql 更有效。但是时间的减少并不是很令人印象深刻。

我还尝试创建一个新的“保存”类,在数据可用时将要保存的数据传递给该类。 “Save”类处理每次将父对象传递给它时创建一个新的tasks.task,所以我认为这或多或少会创建一个连续的对象流以保存在其他线程上,而不是依赖于保存每1000个对象。在“保存”类中,我有以下内容:

Public Class SaveData

Public Sub SaveBDLItem(ByVal o As ParentObject)

    Tasks.Task.Factory.StartNew(
            Sub()
                Dim Object   
                mParentLock.EnterWriteLock()
                Try
                    mcmd1.Parameters.Clear()

                    [code to add parameters to command object]

                    mcmd1.ExecuteNonQuery()
                    'get new key '
                    objectIDx= CInt(mcmd2.ExecuteScalar())
                Finally
                    mBDLLock.ExitWriteLock()
                End Try

                'Now update children'
                mChildLock.EnterWriteLock()
                Try
                    For j = 0 To ParentObject.Children.Count - 1
                        mcmd3.Parameters.Clear()

                        [code to add parameters to command object]

                        mcmd3.ExecuteNonQuery()

                    Next
                Finally
                    mChildLock.ExitWriteLock()
                End Try

            End Sub))

End Sub
.
.
.
End Class

但是,这个实现比我之前的尝试慢得多,实际上似乎是同步运行的。知道为什么这种方法实际上更慢吗?

如果有其他关于如何加快整个过程的想法,我也希望得到反馈。

【问题讨论】:

多线程不会加速缓慢的数据库操作,它通常会使事情变得更糟。批处理所有语句并将它们作为一个执行,或使用 TableDirect 为什么不呢?如果 30% 的时间用于计算,70% 的时间用于写入数据库,那么在我看来,将数据库写入单独的线程将节省高达 30% 的时间。更快的计算可以在后台写入数据库的同时运行,有效地消除了 30% 的计算成本。 @PanagiotisKanavos - 哦,我明白了。我刚刚重读了你的评论。澄清一下,我没有使用多线程来加速数据库操作。我正在使用多线程在计算和数据库操作之间进行多任务处理。对于这种类型的场景,多线程工作得很好,因为它使我能够并行而不是串行地进行计算并写入数据库。 【参考方案1】:

使用 TableDirect API 来避免查询处理器的开销。对于插入和选择,您将看到巨大的速度提升。请记住,sql ce 数据库是单个文件,因此您可以并行执行的操作有限。 我最近有一些关于如何使用 TableDirect api 的博客文章

【讨论】:

【参考方案2】:

Tasks.Task.Factory.StartNew 不一定会创建新的后台线程。它创建了一个可以异步执行的任务。相反,你想使用Parallel.Invoke吗?

【讨论】:

PLINQ 使用任务本身。事实上,PLINQ 也将使用当前线程。它是 Parallel.Invoke,如果只提供一个委托,不会在后台运行

以上是关于具有许多数据库调用的 VB.net 多线程的主要内容,如果未能解决你的问题,请参考以下文章

vb.net 多线程调用另一窗口,假死现象,如何解决

VB.net多线程编程问题

VB.NET多线程入门

VB.net可以多线程控制同一个窗体及其控件吗

vb.net 多线程 访问界面控件

求教VB.net多线程问题