非同步執行 (通知方法)

ODBC 允許非同步執行連線和陳述式作業。 應用程式執行緒可以在非同步模式中呼叫 ODBC 函式,而函式可以在作業完成之前傳回,讓應用程式執行緒執行其他工作。 在 Windows 7 SDK 中,針對非同步陳述式或連線作業,應用程式使用輪詢方法來判斷非同步作業已經完成。 如需詳細資訊,請參閱非同步執行 (輪詢方法)。 從 Windows 8 SDK 開始,您可使用通知方法來判斷非同步作業已經完成。

在輪詢方法中,應用程式每次想得到作業狀態時,都必須呼叫非同步函式。 通知方法類似回撥並於 ADO.NET 中等候。 不過,ODBC 會使用 Win32 事件作為通知物件。

ODBC 資料指標程式庫和 ODBC 非同步通知不能同時使用。 同時設定這兩個屬性會傳回 SQLSTATE S1119 錯誤 (資料指標程式庫和非同步通知不可同時啟用)。

如需適用於驅動程式開發人員的詳細資訊,請參閱非同步函式完成的通知

注意

資料指標程式庫不支援通知方法。 如果應用程式嘗試透過 SQLSetConnectAttr 啟用資料指標程式庫,則啟用通知方法時會收到錯誤訊息。

概觀

以非同步模式呼叫 ODBC 函式時,控制項會立即將傳回碼 SQL_STILL_EXECUTING 傳回給呼叫的應用程式。 應用程式必須重複輪詢函式,直到收到 SQL_STILL_EXECUTING 以外的傳回碼為止。 輪詢迴圈會增加 CPU 使用率,導致許多非同步情節效能不佳。

只要使用通知模型,輪詢模型就會停用。 應用程式不應再次呼叫原始函式。 請呼叫 SQLCompleteAsync Function 來完成非同步作業。 如果應用程式在非同步作業完成之前再次呼叫原始函式,則呼叫會傳回有 SQLSTATE IM017 (輪詢在非同步通知模式中停用) 的 SQL_ERROR。

使用通知模型時,應用程式可呼叫 SQLCancelSQLCancelHandle 來取消陳述式或連線作業。 如果取消要求成功,ODBC 會傳回SQL_SUCCESS。 此訊息不代表函式實際上已取消,其表示系統已處理取消的要求。 函式是否確實取消,需依驅動程式和資料來源而定。 作業取消時,驅動程式管理員仍會針對事件發出訊號。 驅動程式管理員會在傳回碼緩衝區中傳回 SQL_ERROR,其狀態為 SQLSTATE HY008 (作業已取消),表示取消作業已成功。 如果函式已完成正常處理流程,則驅動程式管理員會傳回 SQL_SUCCESS 或 SQL_SUCCESS_WITH_INFO。

舊版行為

支援系統在完成時收到通知的 ODBC 驅動程式管理員版本是 ODBC 3.81。

應用程式 ODBC 版本 驅動程式管理員版本 驅動程式版本 行為
任何 ODBC 版本的新應用程式 ODBC 3.81 ODBC 3.80 驅動程式 如果驅動程式支援這項功能,則應用程式可使用之,否則驅動程式管理員會發生錯誤。
任何 ODBC 版本的新應用程式 ODBC 3.81 ODBC 3.80 前的驅動程式 如果驅動程式不支援這項功能,則驅動程式管理員會發生錯誤。
任何 ODBC 版本的新應用程式 ODBC 3.81 前 任意 應用程式使用這項功能時,舊的驅動程式管理員會將新屬性視為驅動程式特定的屬性,且驅動程式應會發生錯誤。新的驅動程式管理員不會將這些屬性傳遞至驅動程式。

應用程式應先檢查驅動程式管理員版本,再使用這項功能。 否則,如果撰寫不良的驅動程式未發生錯誤,且驅動程式管理員為 ODBC 3.81 之前版本,則行為不會予以定義。

使用案例

本節說明非同步執行和輪詢機制的使用案例。

整合多個 ODBC 來源的資料

資料整合應用程式會以非同步方式從多個資料來源擷取資料。 有些資料來自遠端資料來源,有些則來自本機檔案。 在非同步作業完成之前,應用程式無法繼續。

應用程式可建立事件物件,將之與 ODBC 連線控制代碼或 ODBC 陳述式控制代碼產生關聯,而不是重複輪詢作業來判斷作業是否完成。 接著應用程式會呼叫作業系統同步處理 API,並等候一個事件物件或許多事件物件 (包括 ODBC 事件和其他 Windows 事件)。 ODBC 會在對應的 ODBC 非同步作業完成時發出事件物件的訊號。

Windows 會使用 Win32 事件物件,並為使用者提供統一的程式設計模型。 其他平台上的驅動程式管理員可使用平台特定的事件物件實作。

下列程式碼範例示範如何使用連線和陳述式非同步通知:

// This function opens NUMBER_OPERATIONS connections and executes one query on statement of each connection.  
// Asynchronous Notification is used  
  
#define NUMBER_OPERATIONS 5  
int AsyncNotificationSample(void)  
{  
    RETCODE     rc;  
  
    SQLHENV     hEnv              = NULL;  
    SQLHDBC     arhDbc[NUMBER_OPERATIONS]         = {NULL};  
    SQLHSTMT    arhStmt[NUMBER_OPERATIONS]        = {NULL};  
  
    HANDLE      arhDBCEvent[NUMBER_OPERATIONS]    = {NULL};  
    RETCODE     arrcDBC[NUMBER_OPERATIONS]        = {0};  
    HANDLE      arhSTMTEvent[NUMBER_OPERATIONS]   = {NULL};  
    RETCODE     arrcSTMT[NUMBER_OPERATIONS]       = {0};  
  
    rc = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &hEnv);  
    if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;  
  
    rc = SQLSetEnvAttr(hEnv,  
        SQL_ATTR_ODBC_VERSION,  
        (SQLPOINTER) SQL_OV_ODBC3_80,  
        SQL_IS_INTEGER);  
    if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;  
  
    // Connection operations begin here  
  
    // Alloc NUMBER_OPERATIONS connection handles  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        rc = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &arhDbc[i]);  
        if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;  
    }  
  
    // Enable DBC Async on all connection handles  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        rc= SQLSetConnectAttr(arhDbc[i], SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, (SQLPOINTER)SQL_ASYNC_DBC_ENABLE_ON, SQL_IS_INTEGER);  
        if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;  
    }  
  
    // Application must create event objects  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        arhDBCEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL); // Auto-reset, initial state is not-signaled  
        if (!arhDBCEvent[i]) goto Cleanup;  
    }  
  
    // Enable notification on all connection handles  
    // Event  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        rc= SQLSetConnectAttr(arhDbc[i], SQL_ATTR_ASYNC_DBC_EVENT, arhDBCEvent[i], SQL_IS_POINTER);  
        if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;  
    }  
  
    // Initiate connect establishing  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        SQLDriverConnect(arhDbc[i], NULL, (SQLTCHAR*)TEXT("Driver={ODBC Driver 11 for SQL Server};SERVER=dp-srv-sql2k;DATABASE=pubs;UID=sa;PWD=XYZ;"), SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT);  
    }  
  
    // Can do some other staff before calling WaitForMultipleObjects  
    WaitForMultipleObjects(NUMBER_OPERATIONS, arhDBCEvent, TRUE, INFINITE); // Wait All  
  
    // Complete connect API calls  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        SQLCompleteAsync(SQL_HANDLE_DBC, arhDbc[i], & arrcDBC[i]);  
    }  
  
    BOOL fFail = FALSE; // Whether some connection openning fails.  
  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        if ( !SQL_SUCCEEDED(arrcDBC[i]) )   
            fFail = TRUE;  
    }  
  
    // If some SQLDriverConnect() fail, clean up.  
    if (fFail)  
    {  
        for (int i=0; i<NUMBER_OPERATIONS; i++)  
        {  
            if (SQL_SUCCEEDED(arrcDBC[i]) )   
            {  
                SQLDisconnect(arhDbc[i]); // This is also async  
            }  
            else  
            {  
                SetEvent(arhDBCEvent[i]); // Previous SQLDriverConnect() failed. No need to call SQLDisconnect().  
            }  
        }  
        WaitForMultipleObjects(NUMBER_OPERATIONS, arhDBCEvent, TRUE, INFINITE);   
        for (int i=0; i<NUMBER_OPERATIONS; i++)  
        {  
            if (SQL_SUCCEEDED(arrcDBC[i]) )   
            {     
                SQLCompleteAsync(SQL_HANDLE_DBC, arhDbc[i], &arrcDBC[i]);; // To Complete  
            }  
        }  
  
        goto Cleanup;  
    }  
  
    // Statement Operations begin here  
  
    // Alloc statement handle  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        rc = SQLAllocHandle(SQL_HANDLE_STMT, arhDbc[i], &arhStmt[i]);  
        if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;  
    }  
  
    // Enable STMT Async on all statement handles  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        rc = SQLSetStmtAttr(arhStmt[i], SQL_ATTR_ASYNC_ENABLE, (SQLPOINTER)SQL_ASYNC_ENABLE_ON, SQL_IS_INTEGER);  
        if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;  
    }  
  
    // Create event objects  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        arhSTMTEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL); // Auto-reset, initial state is not-signaled  
        if (!arhSTMTEvent[i]) goto Cleanup;  
    }  
  
    // Enable notification on all statement handles  
    // Event  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        rc= SQLSetStmtAttr(arhStmt[i], SQL_ATTR_ASYNC_STMT_EVENT, arhSTMTEvent[i], SQL_IS_POINTER);  
        if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;  
    }  
  
    // Initiate SQLExecDirect() calls  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        SQLExecDirect(arhStmt[i], (SQLTCHAR*)TEXT("select au_lname, au_fname from authors"), SQL_NTS);  
    }  
  
    // Can do some other staff before calling WaitForMultipleObjects  
    WaitForMultipleObjects(NUMBER_OPERATIONS, arhSTMTEvent, TRUE, INFINITE); // Wait All  
  
    // Now, call SQLCompleteAsync to complete the operation and get return code  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        SQLCompleteAsync(SQL_HANDLE_STMT, arhStmt[i], &arrcSTMT[i]);  
    }  
  
    // Check return values  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        if ( !SQL_SUCCEEDED(arrcSTMT[i]) ) goto Cleanup;  
    }  
  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        //Do some binding jobs here, set SQL_ATTR_ROW_ARRAY_SIZE   
    }  
  
    // Now, initiate fetching  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        SQLFetch(arhStmt[i]);  
    }  
  
    // Can do some other staff before calling WaitForMultipleObjects  
    WaitForMultipleObjects(NUMBER_OPERATIONS, arhSTMTEvent, TRUE, INFINITE);   
  
    // Now, to complete the operations and get return code  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        SQLCompleteAsync(SQL_HANDLE_STMT, arhStmt[i], &arrcSTMT[i]);  
    }  
  
    // Check return code  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        if ( !SQL_SUCCEEDED(arrcSTMT[i]) ) goto Cleanup;  
    }  
  
    // USE fetched data here!!  
  
Cleanup:  
  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        if (arhStmt[NUMBER_OPERATIONS])  
        {  
            SQLFreeHandle(SQL_HANDLE_STMT, arhStmt[i]);  
            arhStmt[i] = NULL;  
        }  
    }  
  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        if (arhSTMTEvent[i])  
        {  
            CloseHandle(arhSTMTEvent[i]);  
            arhSTMTEvent[i] = NULL;  
        }  
    }  
  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        if (arhDbc[i])  
        {  
            SQLFreeHandle(SQL_HANDLE_DBC, arhDbc[i]);  
            arhDbc[i] = NULL;  
        }  
    }  
  
    for (int i=0; i<NUMBER_OPERATIONS; i++)  
    {  
        if (arhDBCEvent[i])  
        {  
            CloseHandle(arhDBCEvent[i]);  
            arhDBCEvent[i] = NULL;  
        }  
    }  
  
    if (hEnv)  
    {  
        SQLFreeHandle(SQL_HANDLE_ENV, hEnv);  
        hEnv = NULL;  
    }  
  
    return 0;  
}  
  

驅動程式是否支援非同步通知的判斷方法

ODBC 應用程式可呼叫 SQLGetInfo,藉此判斷 ODBC 驅動程式是否支援非同步通知。 之後,ODBC 驅動程式管理員會使用 SQL_ASYNC_NOTIFICATION 呼叫驅動程式的 SQLGetInfo

SQLUINTEGER InfoValue;  
SQLLEN      cbInfoLength;  
  
SQLRETURN retcode;  
retcode = SQLGetInfo (hDbc,   
                      SQL_ASYNC_NOTIFICATION,   
                      &InfoValue,  
                      sizeof(InfoValue),  
                      NULL);  
if (SQL_SUCCEEDED(retcode))  
{  
if (SQL_ASYNC_NOTIFICATION_CAPABLE == InfoValue)  
      {  
          // The driver supports asynchronous notification  
      }  
      else if (SQL_ASYNC_NOTIFICATION_NOT_CAPABLE == InfoValue)  
      {  
          // The driver does not support asynchronous notification  
      }  
}  

將 Win32 事件控制代碼與 ODBC 控制代碼產生關聯

應用程式會負責使用對應的 Win32 函式建立 Win32 事件物件。 應用程式可將一個 Win32 事件控制代碼與一個 ODBC 連線控制代碼或一個 ODBC 陳述式控制代碼產生關聯。

連線屬性 SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE 和 SQL_ATTR_ASYNC_DBC_EVENT 會判斷 ODBC 是否以非同步模式執行,以及 ODBC 是否為連線控制代碼啟用通知模式。 陳述式屬性 SQL_ATTR_ASYNC_ENABLE 和 SQL_ATTR_ASYNC_STMT_EVENT 會判斷 ODBC 是否以非同步模式執行,以及 ODBC 是否為陳述式控制代碼啟用通知模式。

SQL_ATTR_ASYNC_ENABLE 或 SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE SQL_ATTR_ASYNC_STMT_EVENT 或 SQL_ATTR_ASYNC_DBC_EVENT Mode
啟用 非 Null 非同步通知
啟用 null 非同步輪詢
停用 任意 同步

應用程式可暫時停用非同步作業模式。 如果停用連線等級非同步作業,則 ODBC 會忽略 SQL_ATTR_ASYNC_DBC_EVENT 的值。 如果停用陳述式等級非同步作業,則 ODBC 會忽略 SQL_ATTR_ASYNC_STMT_EVENT 的值。

SQLSetStmtAttrSQLSetConnectAttr 的同步呼叫

  • SQLSetConnectAttr 支援非同步作業,但叫用 SQLSetConnectAttr 以設定 SQL_ATTR_ASYNC_DBC_EVENT 的過程一律同步。

  • SQLSetStmtAttr 不支援非同步執行。

發生錯誤的情節
如果在連線之前呼叫 SQLSetConnectAttr,驅動程式管理員會無法判斷該使用哪一個驅動程式。 因此,驅動程式管理員會為 SQLSetConnectAttr 傳回成功,但驅動程式可能還無法設定屬性。 應用程式呼叫連線函式時,驅動程式管理員才會設定這些屬性。 驅動程式管理員可能會發生錯誤,因為驅動程式不支援非同步作業。

連線屬性的繼承項目
連線的陳述式通常會繼承連線的屬性。 不過,SQL_ATTR_ASYNC_DBC_EVENT 屬性不可繼承,而且只會影響連線作業。

為讓事件控制代碼與 ODBC 連線控制代碼產生關聯,ODBC 應用程式需呼叫 ODBC API SQLSetConnectAttr,並將 SQL_ATTR_ASYNC_DBC_EVENT 指定為屬性,將事件控制代碼指定為屬性的值。 新的 ODBC 屬性 SQL_ATTR_ASYNC_DBC_EVENT 屬於 SQL_IS_POINTER 型別。

HANDLE hEvent;  
hEvent = CreateEvent(   
            NULL,                // default security attributes  
            FALSE,               // auto-reset event  
            FALSE,               // initial state is non-signaled  
            NULL                 // no name  
            );  

應用程式通常會建立自動重設的事件物件。 ODBC 不會重設事件物件。 應用程式必須先確認該物件未處於信號狀態,再呼叫任何非同步 ODBC 函式。

SQLRETURN retcode;  
retcode = SQLSetConnectAttr ( hDBC,  
                              SQL_ATTR_ASYNC_DBC_EVENT, // Attribute name  
                              (SQLPOINTER) hEvent,      // Win32 Event handle  
                              SQL_IS_POINTER);          // Length Indicator  

SQL_ATTR_ASYNC_DBC_EVENT 是驅動程式中不能設定的僅限驅動程式管理員屬性。

SQL_ATTR_ASYNC_DBC_EVENT 的預設值為 null。 如果驅動程式不支援非同步通知,則取得或設定 SQL_ATTR_ASYNC_DBC_EVENT 會傳回有 SQLSTATE HY092 (不正確的屬性/選項識別碼) 的 SQL_ERROR。

如果 ODBC 連線控制代碼上設定的最後一個 SQL_ATTR_ASYNC_DBC_EVENT 值非為 null,且應用程式已設定 SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE 和 SQL_ASYNC_DBC_ENABLE_ON 屬性來啟用非同步模式,則呼叫任何支援非同步模式的 ODBC 連線函式都能獲得完成的通知。 如果 ODBC 連線控制代碼上設定的最後一個 SQL_ATTR_ASYNC_DBC_EVENT 值為 null,則 ODBC 不會傳送任何通知給應用程式,無論非同步模式啟用與否。

應用程式可在設定 SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE 屬性前後設定 SQL_ATTR_ASYNC_DBC_EVENT。

應用程式可先在 ODBC 連線控制代碼上設定 SQL_ATTR_ASYNC_DBC_EVEN 屬性,再呼叫連線函式 (SQLConnectSQLBrowseConnectSQLDriverConnect)。 由於 ODBC 驅動程式管理員不曉得應用程式會使用哪一個 ODBC 驅動程式,因此會傳回 SQL_SUCCESS。 應用程式呼叫連線函式時,ODBC 驅動程式管理員會檢查驅動程式是否支援非同步通知。 如果驅動程式不支援非同步通知,則 ODBC 驅動程式管理員會傳回有 SQLSTATE S1_118 (驅動程式不支援非同步通知) 的 SQL_ERROR。 如果驅動程式支援非同步通知,則 ODBC 驅動程式管理員會呼叫驅動程式,並設定對應的屬性 SQL_ATTR_ASYNC_DBC_NOTIFICATION_CALLBACK 和 SQL_ATTR_ASYNC_DBC_NOTIFICATION_CONTEXT。

同理,應用程式會在 ODBC 陳述式控制代碼上呼叫 SQLSetStmtAttr,並指定 SQL_ATTR_ASYNC_STMT_EVENT 屬性來啟用或停用陳述式等級非同步通知。 由於建立連線後一律會呼叫陳述式函式,因此只要對應的驅動程式不支援非同步作業,或驅動程式支援非同步作業但不支援非同步通知,則 SQLSetStmtAttr 會立即傳回有 SQLSTATE S1_118 (驅動程式不支援非同步通知) 的 SQL_ERROR。

SQLRETURN retcode;  
retcode = SQLSetStmtAttr ( hSTMT,  
                           SQL_ATTR_ASYNC_STMT_EVENT, // Attribute name   
                           (SQLPOINTER) hEvent,       // Win32 Event handle  
                           SQL_IS_POINTER);           // length Indicator  

可設定為 null 的 SQL_ATTR_ASYNC_STMT_EVENT 是驅動程式中不能設定的僅限驅動程式管理員屬性。

SQL_ATTR_ASYNC_STMT_EVENT 的預設值為 null。 如果驅動程式不支援非同步通知,則取得或設定 SQL_ATTR_ASYNC_ STMT_EVENT 屬性會傳回有 SQLSTATE HY092 (不正確的屬性/選項識別碼) SQL_ERROR。

應用程式不應讓相同的事件控制代碼與超過一個的 ODBC 控制代碼產生關聯。 否則,如果在兩個共用相同事件控制代碼的控制代碼上完成兩個非同步 ODBC 函式的叫用過程,其中一個通知就會遺失。 為避免陳述式控制代碼從連線控制代碼繼承相同的事件控制代碼,只要應用程式在連線控制代碼上設定 SQL_ATTR_ASYNC_STMT_EVENT,ODBC 就會傳回有 SQLSTATE IM016 (無法將陳述式屬性設定至連線控制代碼) 的 SQL_ERROR。

呼叫非同步 ODBC 函式

啟用非同步通知並啟動非同步作業之後,應用程式就可呼叫任何 ODBC 函式。 如果函式屬於支援非同步作業的函式集,則不論函式失敗或成功,應用程式都會在作業完成時收到完成通知。 唯一的例外狀況,是應用程式會呼叫具有不正確連線或陳述式控制代碼的 ODBC 函式。 若是如此,ODBC 不會取得事件控制代碼,也不會將之設定為信號狀態。

應用程式必須確定相關聯的事件物件未處於信號狀態,才能在對應的 ODBC 控制代碼上啟動非同步作業。 ODBC 不會重設事件物件。

從 ODBC 取得通知

應用程式執行緒可呼叫 WaitForSingleObject 來等候一個事件控制代碼,或呼叫 WaitForMultipleObjects 來等候一個陣列的事件控制代碼,並暫止到一或所有事件物件處於信號狀態,或逾時間隔過去為止。

DWORD dwStatus = WaitForSingleObject(  
                        hEvent,  // The event associated with the ODBC handle  
                        5000     // timeout is 5000 millisecond   
);  
  
If (dwStatus == WAIT_TIMEOUT)  
{  
    // time-out interval elapsed before all the events are signaled.   
}  
Else  
{  
    // Call the corresponding Asynchronous ODBC API to complete all processing and retrieve the return code.  
}