2017-06-23 20 views
0

SQLを調べるVB.net 2017 Windowsサービスを作成していますが、行数によっては複数のスレッドが作成されます。これらのスレッドはフォルダを監視し、別のテーブルに戻って報告し、それに応じてデータを記録します。このコードは数年間実行されていて、うまく機能していますが、ここ数日は起動時に実行されるコンソールアプリケーションからウィンドウサービスに切り替えることにしました。これが初めてですWindowsサービスの作成。スレッドからDBに接続するSubを呼び出す

コードを実行してコードを取得しましたが、テストを実行することができなかったため、テストは大きな苦労でした。私はいくつかの変更を行い、重複セクションのいくつかを統合しました。たとえば、データをSQLに書き込むか、SQLからデータをプルするために、4つまたは5つの異なるコード・セクションを作成していました。私はこれらを2つのサブルーチンだけに統合し、スレッドはそれらを連続的に使用します。状況に応じて、プログラムは1〜15スレッドを持つことができます。スレッドをさらに起動すると、問題に遭遇しました。私はすでに、コンソールアプリケーションから自分のコードに文を入れて移植しましたが、新しいプログラムを作成するときにこれらをログテーブルに入れて、 "Open connection"を開こうとしていると不平を言っていました。ここではSQLからデータを引き出し1つのルーチンの例です:

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

一度に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"の代わりに "15"の接続で実行することができないので "1"を使用しました。私は "火星"を有効にしようとしましたが、それでも私はそれを走らせることができませんでした。基本的に、私は最初のSQLコマンドを実行するが、プログラムは戻ってくることはない。 – dbooher2011

+0

戻ってこない場合は、接続リストに戻って接続を解放していないか、トランザクションを使用していて、データベースがデッドロックしています。私は600の接続を開くプロジェクトでこの正確なコードを使用します。 – EJD

+0

それは実行されますが、あなたのコードのように "Pool Limit"を15に設定すると、サービスは本当に始まらないが、それを "1"に設定すると。それが始まり、実行されます。私はデータベースへの1つ以上のアクティブな接続を持つことができないようです。 – dbooher2011

関連する問題