异步执行(通知方法)

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 函数 以完成异步操作。 如果在异步操作完成之前,应用程序再次调用原始函数,则调用将返回 SQL_ERROR,并附带 SQLSTATE IM017(异步通知模式下禁用轮询)。

使用通知模型时,应用程序可以调用 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 PRE-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 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 时,将返回 SQL_ERROR 并显示 SQLSTATE HY092(属性/选项标识符无效)。

如果 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。

应用程序可以在调用连接函数(SQLConnectSQLBrowseConnectSQLDriverConnect)之前,在 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 将立即返回 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.  
}