2017-06-23 34 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