2013-07-16 44 views
0

當我嘗試取消到某個脫機(或未響應)的某個數據庫服務器的異步ADO連接時,ADODB.Connection對象塊的方法在設置的超時期限內。ADO異步連接取消塊

我不喜歡這樣的異步連接:

Set Connection = New ADODB.Connection 
Connection.Provider = "SQLOLEDB" 
Connection.ConnectionTimeout = 60 
Connection.ConnectionString = "Initial Catalog=" & RTrim(DBName) & _ 
           ";Data Source=" & RTrim(DBServerName) & ";Integrated Security = SSPI" 

Connection.Open , , , adAsyncConnect 

再後來撥打以下取消/關閉連接:

If (Connection.State And adStateConnecting) = adStateConnecting Then 
    ' ==== CONNECTION BLOCKS HERE ====== 
    Connection.Cancel 
End If 

If (Connection.State And adStateOpen) = adStateOpen Then 
    Connection.Close 
End If 

Set Connection = Nothing 

有沒有辦法不讓Cancel方法塊?

回答

1

我在最後找到了自己的解決方案。那麼,至少有一個可以接受的解決方法。

首先我創建的可以取消/關閉計時器(由於從Code Project article到一個想法)的連接的模塊:

Option Explicit 

' Timer API: 
Private Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, _ 
    ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) _ 
    As Long 
Private Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, _ 
    ByVal nIDEvent As Long) As Long 

' Collection of connections to cancel 
Private m_connections As Collection 

' The ID of our API Timer: 
Private m_lTimerID As Long 

Private Sub TimerProc(ByVal lHwnd As Long, ByVal lMsg As Long, _ 
    ByVal lTimerID As Long, ByVal lTime As Long) 

On Error GoTo ErrH: 
    Dim cnx As ADODB.Connection 

    ' Remove the timer 
    KillTimer 0, lTimerID 

    If Not m_connections Is Nothing Then 
     With m_connections 
      Do While .Count > 0 
       Set cnx = .Item(1) 
       .Remove 1 

       TryCancelOrCloseConnection cnx 
      Loop 
     End With 

     If m_connections.Count = 0 Then 
      Set m_connections = Nothing 
     End If 
    End If 

    ' Let the next call to CancelOrCloseAsync create a new timer 
    m_lTimerID = 0 
    Exit Sub 
ErrH: 
    ' Let the next call to CancelOrCloseAsync create a new timer 
    m_lTimerID = 0 
    Debug.Print "Error closing connetions timer: " & Err.Description 
End Sub 

Private Sub TryCancelOrCloseConnection(cnx As ADODB.Connection) 
On Error GoTo ErrH 
    If Not cnx Is Nothing Then 
     If (cnx.State And adStateConnecting) = adStateConnecting Then 
      ' The call to Cancel here blocks this execution path (until connection time-out), 
      ' but we assume it internally calls DoEvents, because (even though it blocks here) messages get pumped. 
      cnx.Cancel 
     End If 

     ' If the connection actually made it to an open state, we make sure it is closed 
     If (cnx.State And adStateOpen) = adStateOpen Then 
      cnx.Close 
     End If 
    End If 
    Exit Sub 
ErrH: 
    Debug.Print "ADO Connection Cancel/Close error " & Err.Description 
    ' We deliberately suppress the error here. 
    ' The reason is that accessing the Connection.State property, while there was an error when 
    ' connecting, will raise an error. The idea of this method is simply to make sure we close/cancel 
    ' the pending connection if there was no connection error. 
End Sub 

Public Sub CancelOrCloseAsync(cnx As ADODB.Connection) 
    If Not cnx Is Nothing Then 
     ' Add cnx to the collection of connections to cancel 
     If m_connections Is Nothing Then 
      Set m_connections = New Collection 
     End If 

     m_connections.Add cnx 

     ' Create a timer to start cancelling the connection(s), but only if one is not already busy 
     ' We need to cast the process off to a timer because the Connection.Cancel blocks the 
     ' normal execution path. 
     If m_lTimerID = 0 Then 
      m_lTimerID = SetTimer(0, 0, 1, AddressOf TimerProc) 
     End If 
    End If 
End Sub 

我然後創建稱爲連接替代類clsADOAsyncConn

Private WithEvents Connection As ADODB.Connection 
Private m_Pending As Boolean 
Public Event ConnectComplete(ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection) 

Public Property Get Provider() As String 
    Provider = Connection.Provider 
End Property 

Public Property Let Provider(ByVal val As String) 
    Connection.Provider = val 
End Property 

Public Property Get ConnectionTimeout() As Long 
    ConnectionTimeout = Connection.ConnectionTimeout 
End Property 

Public Property Let ConnectionTimeout(ByVal val As Long) 
    Connection.ConnectionTimeout = val 
End Property 

Public Property Get ConnectionString() As String 
    ConnectionString = Connection.ConnectionString 
End Property 

Public Property Let ConnectionString(ByVal val As String) 
    Connection.ConnectionString = val 
End Property 

Public Sub OpenAsync(Optional ByVal UserID As String = "", Optional ByVal Password As String = "") 
    Connection.Open , UserID, Password, adAsyncConnect 
    m_Pending = True 
End Sub 

Private Sub Class_Initialize() 
    Set Connection = New ADODB.Connection 
End Sub 

Private Sub Class_Terminate() 
    If Not Connection Is Nothing And m_Pending Then 
     ' While the connection is still pending, when the user of this class reminates the refernce 
     ' of this class, we need to cancel it in its own timer loop or else the caller's code will 
     ' block at the point where the refernce to this object is de-referenced. 
     CancelOrCloseAsync Connection 
    End If 
End Sub 

Private Sub Connection_ConnectComplete(ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pConnection As ADODB.Connection) 
    m_Pending = False 

    ' Notify the object client of the connection state 
    RaiseEvent ConnectComplete(pError, adStatus, pConnection) 
End Sub 

然後我更新我的原始連接代碼:

Set Connection = New clsADOAsyncConn 
Connection.Provider = "SQLOLEDB" 
Connection.ConnectionTimeout = 60 
Connection.ConnectionString = "Initial Catalog=" & RTrim(DBName) & _ 
           ";Data Source=" & RTrim(DBServerName) & ";Integrated Security = SSPI" 

Connection.OpenAsync 

然後通過clsADOAsyncConn.ConnectComplete事件重新調用實際連接。

此解決方案唯一的已知問題是,即使它有助於防止正常執行代碼中的塊,但當進程退出時(至少直到最後一個待定連接超時)仍然會導致一個塊)