異步執行(通知方法)
ODBC 允許異步執行連接和語句作業。 應用程式線程可以在異步模式中呼叫 ODBC 函式,而且函式可以在作業完成之前傳回,讓應用程式線程執行其他工作。 在 Windows 7 SDK 中,針對異步語句或連線作業,應用程式會使用輪詢方法判斷異步操作已完成。 如需詳細資訊,請參閱 異步執行(輪詢方法)。 從 Windows 8 SDK 開始,您可以使用通知方法判斷異步操作已完成。
在輪詢方法中,應用程式每次想要作業的狀態時,都需要呼叫異步函式。 通知方法類似於 ADO.NET 中的回呼和等候。 不過,ODBC 會使用 Win32 事件做為通知物件。
ODBC Cursor 應用程式庫和 ODBC 非同步通知無法同時使用。 設定這兩個屬性將會傳回 SQLSTATE S1119 的錯誤(不能同時啟用 Cursor 庫和非同步通知)。
如需驅動程式開發人員的資訊,請參閱 異步函式完成通知。
備註
游標庫不支援通知方法。 如果應用程式在啟用通知方法時,嘗試透過 SQLSetConnectAttr 啟用游標庫,就會收到錯誤訊息。
概述
以異步模式呼叫 ODBC 函式時,控件會立即以傳回碼SQL_STILL_EXECUTING傳回給呼叫的應用程式。 應用程式必須重複輪詢函式,直到傳回SQL_STILL_EXECUTING以外的項目為止。 輪詢迴圈會增加 CPU 使用率,導致許多異步案例的效能不佳。
每當使用通知模型時,輪詢模型就會停用。 應用程式不應該再次呼叫原始函式。 呼叫 SQLCompleteAsync 函式 來完成異步操作。 如果應用程式在異步操作完成之前再次呼叫原始函式,則會返回 SQL_ERROR,以及 SQLSTATE IM017(在異步通知模式下,輪詢已被停用)。
使用通知模型時,應用程式可以呼叫 SQLCancel 或 SQLCancelHandle 來取消語句或連線作業。 如果取消要求成功,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 事件物件,並將為使用者提供統一的程式設計模型。 其他平臺上的驅動程式管理員可以使用這些平臺特有的事件對象實作。
下列程式碼範例示範如何使用連接和 SQL 語句的異步通知方式:
// 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 opening 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 | 模式 |
---|---|---|
啟用 | 非 Null | 異步通知 |
啟用 | 零 | 異步輪詢 |
禁用 | 任何 | 同步 |
應用程式可以暫時停用異步操作模式。 如果連接層級異步操作停用,ODBC 會忽略SQL_ATTR_ASYNC_DBC_EVENT的值。 如果停用語句層級異步操作,ODBC 會忽略 SQL_ATTR_ASYNC_STMT_EVENT的值。
同步呼叫 SQLSetStmtAttr 和 SQLSetConnectAttr
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_ASYNC_DBC_ENABLE_ON 設定屬性SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE來啟用異步模式,則呼叫任何支援異步模式的 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_EVENT屬性(SQLConnect、SQLBrowseConnect或 SQLDriverConnect)。 因為 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 在對應的驅動程式不支援異步操作或驅動程式支援異步操作但不支援異步通知時,會立即傳回 SQL_ERROR,並伴隨 SQLSTATE S1_118 (驅動程式不支援異步通知)。
SQLRETURN retcode;
retcode = SQLSetStmtAttr ( hSTMT,
SQL_ATTR_ASYNC_STMT_EVENT, // Attribute name
(SQLPOINTER) hEvent, // Win32 Event handle
SQL_IS_POINTER); // length Indicator
SQL_ATTR_ASYNC_STMT_EVENT可以設定為 NULL,是驅動程式中不會設定的僅限驅動程式管理員屬性。
SQL_ATTR_ASYNC_STMT_EVENT的預設值為 NULL。 如果驅動程式不支援異步通知,取得或設定 SQL_ATTR_ASYNC_ STMT_EVENT 屬性會傳回 SQL_ERROR 並附帶 SQLSTATE HY092 (無效的屬性/選項標識符)。
應用程式不應該將相同的事件句柄與多個 ODBC 句柄產生關聯。 否則,如果兩個異步 ODBC 函式調用在共用相同事件句柄的兩個句柄上完成,就會遺失一個通知。 為了避免語句句柄從連接句柄繼承相同的事件句柄,如果應用程式在連接句柄上設定 SQL_ATTR_ASYNC_STMT_EVENT,ODBC 會傳回 SQL_ERROR,並顯示 SQLSTATE IM016(無法將語句屬性設定到連接句柄)。
呼叫異步 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.
}