Асинхронное выполнение (метод уведомления)
ODBC позволяет асинхронно выполнять операции подключения и инструкции. Поток приложения может вызывать функцию ODBC в асинхронном режиме, и функция может вернуться до завершения операции, позволяя потоку приложения выполнять другие задачи. В Windows 7 SDK для асинхронных инструкций или операций подключения приложение определяет завершение асинхронной операции с помощью метода опроса. Дополнительные сведения см. в Асинхронное выполнение (метод опроса). Начиная с пакета SDK для Windows 8, можно определить, что асинхронная операция завершена с помощью метода уведомления.
В методе опроса приложения должны вызывать асинхронную функцию каждый раз, когда они хотят получить состояние операции. Метод уведомления аналогичен обратному вызову и ожиданию в ADO.NET. Однако ODBC использует события Win32 в качестве объекта уведомления.
Одновременно нельзя использовать библиотеку курсоров ODBC и асинхронное уведомление ODBC. Установка обоих атрибутов возвращает ошибку с SQLSTATE S1119 (библиотека курсоров и асинхронное уведомление нельзя включить одновременно).
Для получения сведений для разработчиков драйверов см. Уведомление о завершении асинхронной функции.
Заметка
Метод уведомления не поддерживается библиотекой курсоров. Приложение получит сообщение об ошибке, если он пытается включить библиотеку курсоров через SQLSetConnectAttr, когда метод уведомления включен.
Обзор
При вызове функции ODBC в асинхронном режиме управление немедленно возвращается вызывающему приложению с возвращаемым кодом SQL_STILL_EXECUTING. Приложение должно повторно опрашивить функцию, пока она не возвращает что-либо, отличное от SQL_STILL_EXECUTING. Цикл опроса увеличивает загрузку ЦП, что приводит к снижению производительности во многих асинхронных сценариях.
При использовании модели уведомлений модель опроса отключается. Приложения не должны снова вызывать исходную функцию. Вызовите функцию 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, которые будут предоставлять пользователю единую модель программирования. Диспетчеры драйверов на других платформах могут использовать конкретную реализацию объекта события, относящуюся к этим платформам.
В следующем примере кода показано использование асинхронного уведомления подключения и инструкции:
// 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 может определить, поддерживает ли драйвер ODBC асинхронное уведомление, вызвав SQLGetInfo. Диспетчер драйверов ODBC будет вызывать SQLGetInfo драйвера с SQL_ASYNC_NOTIFICATION.
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 | Режим |
---|---|---|
Включить | non-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 вызывает API ODBC 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 (недопустимый идентификатор атрибута или параметра).
Если последнее значение SQL_ATTR_ASYNC_DBC_EVENT, заданное в дескрипторе подключения ODBC, не равно NULL и асинхронный режим включено приложением при установке атрибута SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE на SQL_ASYNC_DBC_ENABLE_ON, вызов любой функции подключения ODBC, поддерживающей асинхронный режим, приведет к получению уведомления о завершении. Если последнее значение SQL_ATTR_ASYNC_DBC_EVENT в дескрипторе подключения ODBC равно NULL, ODBC не отправляет приложению никаких уведомлений, независимо от того, включен ли асинхронный режим.
Приложение может задать SQL_ATTR_ASYNC_DBC_EVENT до или после установки SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE атрибута.
Приложения могут задать атрибут SQL_ATTR_ASYNC_DBC_EVENT для дескриптора подключения ODBC перед вызовом функции подключения (SQLConnect, SQLBrowseConnectили SQLDriverConnect). Так как диспетчер драйверов ODBC не знает, какой драйвер ODBC будет использоваться приложением, он вернет SQL_SUCCESS. Когда приложение вызывает функцию подключения, диспетчер драйверов ODBC проверяет, поддерживает ли драйвер асинхронное уведомление. Если драйвер не поддерживает асинхронное уведомление, диспетчер драйверов ODBC возвращает SQL_ERROR с S1_118 SQLSTATE (драйвер не поддерживает асинхронное уведомление). Если драйвер поддерживает асинхронное уведомление, диспетчер драйверов ODBC вызовет драйвер и задает соответствующие атрибуты SQL_ATTR_ASYNC_DBC_NOTIFICATION_CALLBACK и SQL_ATTR_ASYNC_DBC_NOTIFICATION_CONTEXT.
Аналогичным образом приложение вызывает SQLSetStmtAttr в дескрипторе инструкции ODBC и задает атрибут 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 завершатся на двух дескрипторах, которые используют один и тот же дескриптор событий. Чтобы избежать дескриптора инструкции, наследующего тот же дескриптор событий от дескриптора подключения, ODBC возвращает SQL_ERROR с помощью SQLSTATE IM016 (не удается установить атрибут инструкции в дескриптор подключения), если приложение задает SQL_ATTR_ASYNC_STMT_EVENT на дескриптор подключения.
Вызов асинхронных функций 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.
}