2017-06-23 39 views
0

我正在编写一个VB.net 2017 Windows服务,它查看SQL并根据行数来创建多个线程。这些线程监视文件夹并将其报告回不同的表并相应地记录数据。这段代码已经运行了几年,并且运行得非常好,但在过去的几天里,我决定将它从启动时运行的控制台应用程序切换到窗口服务,这是我第一次写一个Windows服务。调用从线程连接到数据库的Sub

我已经通过并获得了代码工作,但测试是一个主要的痛苦,因为我无法通过代码。我做了一些修改并合并了一些重复部分。例如,我正在编写4或5个不同的代码段,以便将数据写入SQL或从SQL中提取数据。我将它们合并为只有2个子例程,并且这些线程不断使用它们。根据情况,程序可以有1-15个线程,当我开始激活更多线程时,我开始遇到问题。在移植它之前,我已经在控制台应用程序的代码中尝试了一些声明,并且在创建新程序时我只是将它们放到了一个日志表中,而且它正在抱怨我试图打开“打开连接”。下面是从SQL中提取数据一个例程的一个例子:

Public con As New SqlClient.SqlConnection 
Public dsGeneral As New DataSet 
Public dc1 As SqlClient.SqlCommand 
Public pullGeneral As SqlClient.SqlDataAdapter 
Public maxRowsGeneral As Integer 

Public Sub PullGeneralSQL(ByVal SQL As String) 
    Try 
     If (con.State = ConnectionState.Closed) Then 
      con.Open() 
     End If 

     dsGeneral.Tables.Clear() 

     pullGeneral = New SqlClient.SqlDataAdapter(SQL, con) 
     pullGeneral.Fill(dsGeneral, "General") 

     maxRowsGeneral = dsGeneral.Tables("General").Rows.Count 
    Catch ex As Exception 
     Msg(ex.Message, "Error") 
     maxRowsGeneral = 0 
    End Try 

    con.Close() 
End Sub 

我也收到错误说连接已经关闭为好。我假设另一个线程完成了连接,并在线程处于任务中间时关闭了连接。

我的问题是,处理这个问题的最好方法是什么?

+1

您是否只需要一个线程一次打开数据库连接?如果是这样,那么你可能想在打开和关闭连接的代码周围使用'SyncLock',一次只限制一个线程。否则,请确保您在连接字符串中启用了“MultipleActiveResultSets”。 – jmcilhinney

+0

是否有最多15个线程? – EJD

+0

jmcihinney - 我基本上想要一个线程来打开连接,做它的事情,然后关闭连接。我在想,每个线程都可以独立连接,不会引起对方的问题。 – dbooher2011

回答

0

我以前遇到过这个问题,即使最后有连接池打开和关闭以及打开和关闭连接时数据库最终会抛出错误。在我的情况下,它一次又一次是600个线程。工作了一段时间,然后抛出相同类型的错误。

我的解决方案是创建我自己的连接池。

Public Class Form1 

    Public dsGeneral As New DataSet 
    Public dc1 As SqlClient.SqlCommand 
    Public pullGeneral As SqlClient.SqlDataAdapter 
    Public maxRowsGeneral As Integer 

    Public Sub PullGeneralSQL(ByVal SQL As String) 

     'Get a connection from the list..Thread safe 
     'Your thread will wait here until there is a connection to grab. 
     'Since your max is 15 the 16++ threads will all wait their turn 
     Dim con As SqlClient.SqlConnection = ConnectionManager.WaitForConnection() 

     Try 
      dsGeneral.Tables.Clear() 

      pullGeneral = New SqlClient.SqlDataAdapter(SQL, con) 
      pullGeneral.Fill(dsGeneral, "General") 

      maxRowsGeneral = dsGeneral.Tables("General").Rows.Count 
     Catch ex As Exception 
      Msg(ex.Message, "Error") 
      maxRowsGeneral = 0 
     End Try 

     'Put the connection back into the list 
     'Allows another thread to start 
     ConnectionManager.ReleaseConnection(con) 
    End Sub 

End Class 

Public Class ConnectionManager 
    Public Shared poolLimit As Integer = 15 'Maximum number of connections to open. 
    Public Shared connectionString As String = "PUT YOUR CONNECTION STRING HERE" 

    'Since this is static it will create 15 connections and add them to the list when the service starts 
    Private Shared ConnectionList As New IThreadPool(Of SqlClient.SqlConnection)(Function() 
                        Dim connection As New SqlClient.SqlConnection(connectionString) 
                        connection.Open() 
                        Return connection 
                       End Function, poolLimit) 

    ''' <summary> 
    ''' Gets the pool count. 
    ''' Purely for information to get the number of connections left in the list 
    ''' </summary> 
    Public ReadOnly Property PoolCount() As Integer 
     Get 
      Return ConnectionList.PoolCount 
     End Get 
    End Property 

    ''' <summary> 
    ''' Waits until there is a free connection in the list 
    ''' When there is a free connection grab it and hold it 
    ''' </summary> 
    Public Shared Function WaitForConnection() As SqlClient.SqlConnection 
     Try 
      Return ConnectionList.GetObject() 
     Catch ex As Exception 
      'only during close 
      Throw ex 
     End Try 
     Return Nothing 
    End Function 

    ''' <summary> 
    ''' Releases the connection. 
    ''' Put the connection back into the list. 
    ''' </summary> 
    Public Shared Sub ReleaseConnection(connection As SqlClient.SqlConnection) 
     Try 
      ConnectionList.PutObject(connection) 
     Catch ex As Exception 
      'only during close 
      Throw ex 
     End Try 
    End Sub 

    ''' <summary> 
    ''' Disposes this instance. 
    ''' Make sure to dispose when the service shuts down or your connections will stay active. 
    ''' </summary> 
    Public Shared Sub Dispose() 
     ConnectionList.Dispose() 
    End Sub 

End Class 

Public Class IThreadPool(Of T) 
    Private connections As System.Collections.Concurrent.BlockingCollection(Of T) 
    Private objectGenerator As Func(Of T) 

    Public Sub New(objectGenerator As Func(Of T), boundedCapacity As Integer) 

     If objectGenerator Is Nothing Then 
      Throw New ArgumentNullException("objectGenerator") 
     End If 

     connections = New System.Collections.Concurrent.BlockingCollection(Of T)(New System.Collections.Concurrent.ConcurrentBag(Of T)(), boundedCapacity) 

     Me.objectGenerator = objectGenerator 

     Task.Factory.StartNew(Function() 
            If connections.Count < boundedCapacity Then 
             Parallel.[For](0, boundedCapacity, Function(i) 
                      Try 
                       If connections.Count < boundedCapacity Then 
                        connections.Add(Me.objectGenerator()) 
                       End If 
                      Catch ex As Exception 
                       'only error during close 
                      End Try 
                     End Function) 

             Try 
              While connections.Count < boundedCapacity 
               connections.Add(Me.objectGenerator()) 
              End While 
             Catch ex As Exception 
              'only error during close 
             End Try 
            End If 
           End Function) 
    End Sub 

    Public ReadOnly Property PoolCount() As Integer 
     Get 
      Return If(connections IsNot Nothing, connections.Count, 0) 
     End Get 
    End Property 

    Public Function GetObject() As T 
     Return connections.Take() 
    End Function 

    Public Sub PutObject(item As T) 
     connections.Add(item) 
    End Sub 

    Public Sub Dispose() 
     connections.Dispose() 
    End Sub 
End Class 
+0

你的代码工作美观,但不是“15”我用“1”,因为我不能让它与“15”连接运行。我试图启用“火星”,但它仍然不允许我运行它。基本上,我得到第一个SQL命令运行,但程序将永远不会回来。 – dbooher2011

+0

如果它没有回来,那么您不会将连接释放回连接列表,或者您正在使用事务并且数据库处于死锁状态。我在打开600个连接的项目中使用此确切代码。 – EJD

+0

它运行,但如果我将“池限制”设置为15就像在您的代码中一样,服务从未真正启动,但是如果将其设置为“1”。它会启动并运行。看起来我不能有更多的1活动连接到数据库。 – dbooher2011