异步执行(通知方法)
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 函数以完成异步操作。 如果应用程序在异步操作完成之前再次调用原始函数,则调用将返回包含 SQLSTATE IM017 的 SQL_ERROR(在异步通知模式下禁用轮询)。
使用通知模型时,应用程序可以调用 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 事件对象,这将为用户提供统一的编程模型。 其他平台上的驱动程序管理器可以使用特定于这些平台的事件对象实现。
以下代码示例演示如何使用连接和语句异步通知:
// 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 | 模型 |
---|---|---|
启用 | 非 Null | 异步通知 |
启用 | Null | 异步轮询 |
禁用 | any | 同步 |
应用程序可以暂时禁用异步操作模式。 如果禁用连接级异步操作,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。
在调用连接函数(SQLConnect、SQLBrowseConnect 或 SQLDriverConnect)之前,应用程序可以在 ODBC 连接句柄上设置 SQL_ATTR_ASYNC_DBC_EVENT 属性。 由于 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
SQL_ATTR_ASYNC_STMT_EVENT(可设置为 NULL)是只有驱动程序管理器才具备的属性,将不会在驱动程序中设置。
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.
}