异步执行和事件通知

ODBC 允许异步执行连接和语句操作。 应用程序线程可以在异步模式下调用 ODBC 函数,该函数可以在操作完成之前返回,从而允许应用程序线程执行其他任务。

对于异步执行的结果确定,有轮询和通知两种方法进行:

轮询方法

默认情况下,驱动程序同步执行 ODBC 函数;也就是说,应用程序调用函数,驱动程序在完成执行函数之前不会将控制权返回给应用程序。 但是,某些函数可以异步执行;也就是说,应用程序调用 函数,驱动程序在经过最少的处理后,将控制权返回给应用程序。 然后,当第一个函数仍在执行时,应用程序可以调用其他函数。

大多数主要在数据源上执行的函数都支持异步执行,例如用于建立连接、准备和执行 SQL 语句、检索元数据、提取数据和提交事务的函数。 在数据源上执行的任务需要很长时间(例如登录过程或针对大型数据库的复杂查询)时,它最有用。

当应用程序使用启用了异步处理的语句或连接执行函数时,驱动程序将执行最少量的处理 (例如,) 检查错误参数,手动处理数据源,并使用SQL_STILL_EXECUTING返回代码将控制权返回给应用程序。 然后,应用程序执行其他任务。 为了确定异步函数何时完成,应用程序会定期轮询驱动程序,方法是使用与最初使用的相同参数调用该函数。 如果函数仍在执行,则返回SQL_STILL_EXECUTING;如果已完成执行,则返回同步执行时会返回的代码,例如SQL_SUCCESS、SQL_ERROR或SQL_NEED_DATA。

函数是同步执行还是异步执行是特定于驱动程序的。 例如,假设结果集元数据缓存在驱动程序中。 在这种情况下,执行 SQLDescribeCol 需要很少的时间,驱动程序应该只执行函数,而不是人为地延迟执行。 另一方面,如果驱动程序需要从数据源检索元数据,则应在应用程序执行此操作时将控制权返回给应用程序。 因此,应用程序在首次异步执行函数时,必须能够处理除 SQL_STILL_EXECUTING 以外的返回代码。

异步执行语句操作

若要指定使用特定语句执行的函数以异步方式执行,应用程序使用 SQL_ATTR_ASYNC_ENABLE 属性调用 SQLSetStmtAttr 并将其设置为SQL_ASYNC_ENABLE_ON。 如果支持连接级异步处理,则 SQL_ATTR_ASYNC_ENABLE 语句属性为只读,其值与为其分配语句的连接的连接属性相同。 无论语句属性的值是在语句分配时间还是以后设置,它都是特定于驱动程序的。 尝试设置它将返回SQL_ERROR,

若要指定使用特定连接执行的函数以异步方式执行,应用程序使用 SQL_ATTR_ASYNC_ENABLE 属性调用 SQLSetConnectAttr 并将其设置为SQL_ASYNC_ENABLE_ON。 将为异步执行启用在连接上分配的所有未来语句句柄;此操作是否启用现有语句句柄是驱动程序定义的。 如果将 SQL_ATTR_ASYNC_ENABLE 设置为 SQL_ASYNC_ENABLE_OFF,则连接上的所有语句都处于同步模式。 如果在连接上存在活动语句时启用异步执行,则返回错误。

若要确定驱动程序可以在给定连接上支持的异步模式下的最大活动并发语句数,应用程序使用 SQL_MAX_ASYNC_CONCURRENT_STATEMENTS(默认为0) 选项调用 SQLGetInfo 。

轮询方法工作示例:

SQLHSTMT  hstmt1;
SQLRETURN rc;

// 指定语句句柄为异步工作模式
SQLSetStmtAttr(hstmt1, SQL_ATTR_ASYNC_ENABLE, SQL_ASYNC_ENABLE_ON, 0);

// 异步执行select语句
while ((rc=SQLExecDirect(hstmt1,"SELECT * FROM Orders",SQL_NTS))==SQL_STILL_EXECUTING) {
   // 如果返回值是SQL_STILL_EXECUTING则继续循环,否则可以做其他事情
   // 不能用hstmt1句柄,因为他正在异步执行语句
}

当函数异步执行时,应用程序可以对任何其他语句调用函数。 应用程序还可以对任何连接(与异步语句关联的连接除外)调用函数。 但是,在语句操作返回SQL_STILL_EXECUTING后,应用程序只能调用原始函数和以下函数 (语句句柄或其关联的连接、环境句柄) :

  • SQLCancel
  • 语句句柄上的 SQLCancelHandle ()
  • SQLGetDiagField
  • SQLGetDiagRec
  • SQLAllocHandle
  • SQLGetEnvAttr
  • SQLGetConnectAttr
  • SQLDataSources
  • SQLDrivers
  • SQLGetInfo
  • SQLGetFunctions
  • SQLNativeSql

当应用程序调用函数以确定它是否仍在异步执行时,它必须使用原始语句句柄。 这是因为异步执行是按语句跟踪的。 应用程序还必须为其他参数(原始参数会提供)的有效值,才能在驱动程序管理器中获取过去的错误检查。 但是,在驱动程序检查语句句柄并确定语句正在异步执行之后,它会忽略所有其他参数。

当函数以异步方式执行时(即在函数返回SQL_STILL_EXECUTING之后,在返回其他代码之前),应用程序可以通过调用具有相同语句句柄的 SQLCancel 或 SQLCancelHandle 来取消它。 这不能保证取消函数执行。 例如, 函数可能已完成。 此外, SQLCancel 或 SQLCancelHandle 返回的代码仅指示取消函数的尝试是否成功,而不是是否实际取消了函数。 为了确定是否取消了该函数,应用程序再次调用该函数。 如果取消了函数,则返回SQL_ERROR,SQLSTATE HY008 (操作已取消) 。 如果未取消该函数,它将返回另一个代码,例如SQL_SUCCESS、SQL_STILL_EXECUTING或使用不同的 SQLSTATE SQL_ERROR。

若要在驱动程序支持语句级异步处理时禁用特定语句的异步执行,应用程序使用 SQL_ATTR_ASYNC_ENABLE 属性调用 SQLSetStmtAttr 并将其设置为SQL_ASYNC_ENABLE_OFF。 如果驱动程序支持连接级异步处理,则应用程序会调用 SQLSetConnectAttr 将SQL_ATTR_ASYNC_ENABLE设置为 SQL_ASYNC_ENABLE_OFF,这会禁用对连接的所有语句的异步执行。

应用程序应在原始函数的重复循环中处理诊断记录。 如果在执行异步函数时调用 SQLGetDiagField 或 SQLGetDiagRec ,它将返回诊断记录的当前列表。 每次重复原始函数调用时,它都会清除以前的诊断记录。

异步执行连接操作

在 ODBC 3.8 之前,允许对语句相关的操作(如准备、执行和提取)以及目录元数据操作进行异步执行。 从 ODBC 3.8 开始,还可以对连接相关操作(例如连接、断开连接、提交和回滚)执行异步执行。

在以下情况下,异步执行连接操作很有用:

  • 当少量线程以非常高的数据速率管理大量设备时。 为了最大限度地提高响应能力和可伸缩性,所有操作都是异步的。
  • 如果要在多个连接上重叠数据库操作,以缩短传输时间。
  • 高效的异步 ODBC 调用和取消连接操作的功能使应用程序能够允许用户取消任何缓慢的操作,而无需等待超时。

对连接句柄执行以下函数现在可以异步执行:

  • SQLBrowseConnect
  • SQLConnect
  • SQLDisconnect
  • SQLDriverConnect
  • SQLEndTran
  • SQLSetConnectAttr

若要确定驱动程序是否支持对这些函数的异步操作,应用程序使用 SQL_ASYNC_DBC_FUNCTIONS 调用 SQLGetInfo 。 如果支持异步操作,则返回SQL_ASYNC_DBC_CAPABLE。 如果不支持异步操作,则返回SQL_ASYNC_DBC_NOT_CAPABLE。

若要指定使用特定连接执行的函数以异步方式执行,应用程序将调用 SQLSetConnectAttr 并将 SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE 属性设置为 SQL_ASYNC_DBC_ENABLE_ON。 在建立连接之前设置连接属性始终同步执行。 此外,使用 SQLSetConnectAttr 设置连接属性SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE的操作始终同步执行。

应用程序可以在建立连接之前启用异步操作。 由于驱动程序管理器在建立连接之前无法确定要使用的驱动程序,因此驱动程序管理器将始终在 SQLSetConnectAttr 中返回成功。 但是,如果 ODBC 驱动程序不支持异步操作,则可能无法连接。

通常,最多可以有一个与特定连接句柄或语句句柄关联的异步执行函数。 但是,连接句柄可以有多个关联的语句句柄。 如果连接句柄上没有异步操作执行,则关联的语句句柄可以执行异步操作。 同样,如果任何关联的语句句柄上都没有正在进行的异步操作,则可以对连接句柄执行异步操作。 尝试使用当前正在执行异步操作的句柄执行异步操作将返回 HY010“函数序列错误”。

如果连接操作返回SQL_STILL_EXECUTING,则应用程序只能为该连接句柄调用原始函数和以下函数:

  • 连接句柄上的 SQLCancelHandle ()
  • SQLGetDiagField
  • SQLGetDiagRec
  • SQLAllocHandle (分配 ENV/DBC)
  • SQLAllocHandleStd (分配 ENV/DBC)
  • SQLGetEnvAttr
  • SQLGetConnectAttr
  • SQLDataSources
  • SQLDrivers
  • SQLGetInfo
  • SQLGetFunctions

应用程序应在原始函数的重复循环中处理诊断记录。 如果在执行异步函数时调用 SQLGetDiagField 或 SQLGetDiagRec,它将返回诊断记录的当前列表。 每次重复原始函数调用时,都会清除以前的诊断记录。

如果正在异步打开或关闭连接,则当应用程序在原始函数调用中收到SQL_SUCCESS或SQL_SUCCESS_WITH_INFO时,操作将完成。

已向 ODBC 3.8 SQLCancelHandle 添加了一个新函数。 此函数取消六个连接函数 (SQLBrowseConnect、 SQLConnect、 SQLDisconnect、 SQLDriverConnect、 SQLEndTran 和 SQLSetConnectAttr) 。 应用程序应调用 SQLGetFunctions 来确定驱动程序是否支持 SQLCancelHandle。 与 SQLCancel 一样,如果 SQLCancelHandle 返回成功,并不意味着操作已取消。 应用程序应再次调用原始函数,以确定操作是否已取消。 SQLCancelHandle 允许取消对连接句柄或语句句柄的异步操作。 使用 SQLCancelHandle 取消语句句柄上的操作与调用 SQLCancel 相同。

无需同时支持 SQLCancelHandle 和异步连接操作。 驱动程序可以支持异步连接操作,但不支持 SQLCancelHandle,反之亦然。

使用 ODBC 3.8 驱动程序和 ODBC 3.8 驱动程序管理器的 ODBC 3.x 和 ODBC 2.x 应用程序也可以使用异步连接操作和 SQLCancelHandle 。

通知方法

ODBC 允许异步执行连接和语句操作。 应用程序线程可以在异步模式下调用 ODBC 函数,该函数可以在操作完成之前返回,从而允许应用程序线程执行其他任务。 在 Windows 7 SDK 中,对于异步语句或连接操作,应用程序使用轮询方法确定异步操作已完成。 有关详细信息,请参阅 异步执行 (轮询方法) 。 从 Windows 8 SDK 开始,可以使用通知方法确定异步操作已完成。

在轮询方法中,每次需要操作状态时,应用程序都需要调用异步函数。 通知方法类似于回调和等待 ADO.NET。 但是,ODBC 使用 Win32 事件作为通知对象。

不能同时使用 ODBC 游标库和 ODBC 异步通知。 设置这两个属性将返回一个错误,其中 SQLSTATE S1119 (游标库和异步通知不能同时启用) 。

在异步模式下调用 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 应用程序可以通过调用 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)
      {
          // 驱动支持异步通知
      }
      else if (SQL_ASYNC_NOTIFICATION_NOT_CAPABLE == InfoValue)
      {
          // 驱动不支持异步通知
      }
}

连接属性SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE和SQL_ATTR_ASYNC_DBC_EVENT确定 ODBC 是否在异步模式下执行,以及 ODBC 是否为连接句柄启用通知模式。 语句属性SQL_ATTR_ASYNC_ENABLE和SQL_ATTR_ASYNC_STMT_EVENT确定 ODBC 是否在异步模式下执行,以及 ODBC 是否为语句句柄启用通知模式。

应用程序可以暂时禁用异步操作模式。 如果禁用连接级别异步操作,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,                // 默认安全属性
            FALSE,               // 自动重置
            FALSE,               // 初始化状态
            NULL                 // 名称
            );

通常,应用程序会创建自动重置事件对象。 ODBC 不会重置事件对象。 在调用任何异步 ODBC 函数之前,应用程序必须确保对象未处于信号状态。

SQLRETURN retcode;
retcode = SQLSetConnectAttr ( hDBC,
                              SQL_ATTR_ASYNC_DBC_EVENT, // 属性名称
                              (SQLPOINTER) hEvent,      // Win32 Event 句柄
                              SQL_IS_POINTER);          //句柄类型

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 返回SQL_ERROR S1_118 (驱动程序不支持异步通知) 。 如果驱动程序支持异步通知,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, // 属性名称
                           (SQLPOINTER) hEvent,       // Win32 Event 句柄
                           SQL_IS_POINTER);           // 句柄类型

SQL_ATTR_ASYNC_STMT_EVENT(可设置为 NULL)是驱动程序管理器的仅限驱动程序管理器的属性,不会在驱动程序中设置。

SQL_ATTR_ASYNC_STMT_EVENT的默认值为 NULL。 如果驱动程序不支持异步通知,则获取或设置 SQL_ATTR_ASYNC_STMT_EVENT 属性将返回SQL_ERROR, (SQLSTATE HY092) 属性/选项标识符无效。

应用程序不应将同一事件句柄与多个 ODBC 句柄相关联。 否则,如果在共享同一事件句柄的两个句柄上完成两个异步 ODBC 函数调用,则一个通知将丢失。 为了避免语句句柄从连接句柄继承相同的事件句柄,ODBC 使用 SQLSTATE IM016 返回SQL_ERROR (如果应用程序设置连接句柄上的SQL_ATTR_ASYNC_STMT_EVENT,则无法将语句属性设置为连接句柄) 。

调用异步 ODBC 函数

启用异步通知并启动异步操作后,应用程序可以调用任何 ODBC 函数。 如果函数属于支持异步操作的函数集,则无论该函数是失败还是成功,应用程序都将在操作完成时收到完成通知。 唯一的例外是应用程序调用具有无效连接或语句句柄的 ODBC 函数。 在这种情况下,ODBC 不会获取事件句柄并将其设置为信号状态。

在相应的 ODBC 句柄上启动异步操作之前,应用程序必须确保关联的事件对象处于非信号状态。 ODBC 不会重置事件对象。

从 ODBC 获取通知

应用程序线程可以调用 WaitForSingleObject 以等待一个事件句柄,或调用 WaitForMultipleObject 以 等待事件句柄数组并暂停,直到一个或所有事件对象收到信号或超时间隔已过。

DWORD dwStatus = WaitForSingleObject(
                        hEvent,  // 事件句柄
                        5000     // 超时时间(毫秒)
);

If (dwStatus == WAIT_TIMEOUT)
{
    // 如果状态超时,则退出
}
Else
{
    // 正确获得通知,此时可以调用SQLCompleteAsync接口获得执行状态。
}

SQLCompleteAsync属于odbc驱动管理器提供的接口,数据库驱动程序不提供这个接口。

异步执行和通知的示例

#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;

    // 连接操作开始

    // 分配连接句柄
    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        rc = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &arhDbc[i]);
        if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
    }

    // 将连接异步开关开启
    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;
    }

    // 应用必须创建事件句柄对象
    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;
    }

    // 开启异步通知开关
    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;
    }

    // 异步执行数据库连接
    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);
    }

    // 等待所有连接句柄的事件通知返回
    WaitForMultipleObjects(NUMBER_OPERATIONS, arhDBCEvent, TRUE, INFINITE); // Wait All

    // 完成异步调用,并获得异步执行的返回值
    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        SQLCompleteAsync(SQL_HANDLE_DBC, arhDbc[i], & arrcDBC[i]);
    }

    BOOL fFail = FALSE; // 是否有连接失败

    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        if ( !SQL_SUCCEEDED(arrcDBC[i]) )
            fFail = TRUE;
    }

    // 如果有连接失败,清理掉这些链接
    if (fFail)
    {
        for (int i=0; i<NUMBER_OPERATIONS; i++)
        {
            if (SQL_SUCCEEDED(arrcDBC[i]) )
            {
                SQLDisconnect(arhDbc[i]); // 这个也是异步操作
            }
            else
            {
                SetEvent(arhDBCEvent[i]); // 如果是SQLDriverConnect() 失败.不需要调用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]);; //完成操作
            }
        }

        goto Cleanup;
    }

    // 语句执行开始

    // 分配语句句柄
    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        rc = SQLAllocHandle(SQL_HANDLE_STMT, arhDbc[i], &arhStmt[i]);
        if ( !SQL_SUCCEEDED(rc) ) goto Cleanup;
    }

    // 开启语句句柄的异步模式
    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;
    }

    // 创建事件对象
    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;
    }

    // 开启语句句柄的事件通知开关
    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;
    }

    // 异步调用  SQLExecDirect 执行语句
    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        SQLExecDirect(arhStmt[i], (SQLTCHAR*)TEXT("select au_lname, au_fname from authors"), SQL_NTS);
    }

    // 等待事件通知返回
    WaitForMultipleObjects(NUMBER_OPERATIONS, arhSTMTEvent, TRUE, INFINITE); // Wait All

    // 调用SQLCompleteAsync获得各个SQLExecDirect返回值
    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        SQLCompleteAsync(SQL_HANDLE_STMT, arhStmt[i], &arrcSTMT[i]);
    }

    // 检查返回值
    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        if ( !SQL_SUCCEEDED(arrcSTMT[i]) ) goto Cleanup;
    }

    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        //做一些获取数据的一些工作,在这里
    }

    // 异步执行SQLFetch操作
    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        SQLFetch(arhStmt[i]);
    }

    // 等待所有SQLFetch完成
    WaitForMultipleObjects(NUMBER_OPERATIONS, arhSTMTEvent, TRUE, INFINITE);

    // 获得SQLFetch操作的返回结果
    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        SQLCompleteAsync(SQL_HANDLE_STMT, arhStmt[i], &arrcSTMT[i]);
    }

    // 检查结果
    for (int i=0; i<NUMBER_OPERATIONS; i++)
    {
        if ( !SQL_SUCCEEDED(arrcSTMT[i]) ) goto Cleanup;
    }

    // 在这里可以使用获取到的数据

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;
}

注解

通知方法需要驱动管理器和数据库驱动协同工作才能完成,无法单独调用数据库驱动来完成;