Execução assíncrona (método de notificação)
O ODBC permite a execução assíncrona de operações de conexão e instrução. Um thread de aplicativo pode chamar uma função ODBC no modo assíncrono e a função pode retornar antes que a operação seja concluída, permitindo que o thread de aplicativo execute outras tarefas. No SDK do Windows 7, para operações de conexão ou de instrução assíncrona, um aplicativo usava o método de sondagem para determinar que a operação assíncrona estava concluída. Para obter mais informações, consulte Execução assíncrona (método de sondagem). A partir do SDK do Windows 8, você pode determinar se uma operação assíncrona está concluída usando o método de notificação.
No método de sondagem, os aplicativos precisam chamar a função assíncrona sempre que quiserem o status da operação. O método de notificação é semelhante ao retorno de chamada e espera em ADO.NET. No entanto, o ODBC usa eventos Win32 como o objeto de notificação.
A Biblioteca de cursores ODBC e a notificação assíncrona do ODBC não podem ser usadas ao mesmo tempo. A configuração de ambos os atributos retornará um erro com o SQLSTATE S1119 (a Biblioteca de cursores e a Notificação assíncrona não podem ser habilitadas ao mesmo tempo).
Consulte Notificação de conclusão de função assíncrona para obter informações para desenvolvedores de driver.
Observação
O método de notificação não é compatível com a biblioteca de cursores. Um aplicativo receberá a mensagem de erro se tentar habilitar a biblioteca de cursores por meio do SQLSetConnectAttr quando o método de notificação for habilitado.
Visão geral
Quando uma função ODBC é chamada no modo assíncrono, o controle é retornado ao aplicativo que está fazendo a chamada imediatamente com o código de retorno SQL_STILL_EXECUTING. O aplicativo deve pesquisar repetidamente a função até que ela retorne algo diferente de SQL_STILL_EXECUTING. O loop de sondagem aumenta a utilização da CPU, causando baixo desempenho em muitos cenários assíncronos.
Sempre que o modelo de notificação é usado, o modelo de sondagem é desabilitado. Os aplicativos não devem chamar a função original novamente. Chame a função SQLCompleteAsync para completar a operação assíncrona. Se um aplicativo chamar a função original novamente antes que a operação assíncrona seja concluída, a chamada retornará SQLSTATE IM017 (Sondagem desabilitada no modo de notificação assíncrona).
Ao usar o modelo de notificação, o aplicativo pode chamar SQLCancel ou SQLCancelHandle para cancelar uma operação de instrução ou conexão. Se a solicitação de cancelamento for bem-sucedida, o ODBC retornará SQL_SUCCESS. Essa mensagem não indica que a função foi realmente cancelada, mas indica que a solicitação de cancelamento foi processada. O cancelamento da função depende do driver e da fonte de dados. Quando uma operação é cancelada, o Gerenciador de Driver ainda sinalizará o evento. O Gerenciador de Driver retorna SQL_ERROR no buffer do código de retorno e o estado é SQLSTATE HY008 (Operação cancelada) para indicar que o cancelamento foi bem sucedido. Se a função concluir seu processamento normal, o Gerenciador de Driver retorna SQL_SUCCESS ou SQL_SUCCESS_WITH_INFO.
Comportamento de nível inferior
O Gerenciador de Driver ODBC versão 3.81 é compatível com essa notificação de conclusão.
Versão do aplicativo ODBC | Versão do Gerenciador de Driver | Versão do driver | Comportamental |
---|---|---|---|
Novo aplicativo de qualquer versão ODBC | ODBC 3.81 | Driver ODBC 3.80 | O aplicativo pode usar esse recurso se o driver for compatível com o recurso, caso contrário, o Gerenciador de Driver falhará. |
Novo aplicativo de qualquer versão ODBC | ODBC 3.81 | Pré-driver ODBC 3.80 | O Gerenciador de Driver falhará se o driver não for compatível com esse recurso. |
Novo aplicativo de qualquer versão ODBC | Pré-ODBC 3.81 | Qualquer | Quando o aplicativo usa este recurso, um antigo Gerenciador de Driver considerará os novos atributos como atributos específicos do driver, e o driver deverá apresentar um erro. Um novo Gerenciador de Driver não passará esses atributos para o driver. |
Um aplicativo deve verificar a versão do Gerenciador de Driver antes de usar esse recurso. Caso contrário, se um driver mal escrito não falhar e a versão do Gerenciador de Driver for anterior ao ODBC 3.81, o comportamento será indefinido.
Casos de uso
Esta seção mostra os casos de uso para execução assíncrona e o mecanismo de sondagem.
Integre dados de diversas fontes ODBC
Um aplicativo de integração de dados busca dados de forma assíncrona de várias fontes de dados. Alguns dos dados são de fontes de dados remotas e outros são de arquivos locais. O aplicativo não pode prosseguir até que as operações assíncronas sejam concluídas.
Em vez de pesquisar repetidamente uma operação para determinar se ela está concluída, o aplicativo pode criar um objeto de evento e associá-lo a um identificador de conexão ODBC ou a um identificador de instrução ODBC. Em seguida, o aplicativo chama as APIs de sincronização do sistema operacional para esperar por um objeto de evento ou em vários objetos de evento (eventos ODBC e outros eventos do Windows). O ODBC sinalizará o objeto de evento quando a operação assíncrona do ODBC correspondente for concluída.
No Windows, objetos de evento Win32 são usados e isso fornece ao usuário um modelo de programação unificado. Os Gerentes de Driver de outras plataformas podem usar a implementação de objeto de evento específica dessas plataformas.
O seguinte exemplo de código demonstra o uso da notificação assíncrona de conexão e instrução:
// 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;
}
Determinar se um driver é compatível com notificações assíncronas
Um aplicativo ODBC pode chamar SQLGetInfo para determinar se um driver ODBC é compatível com notificações assíncronas. O Gerenciador de Driver ODBC consequentemente chamará o SQLGetInfo do driver com 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
}
}
Associar um identificador de evento Win32 com um identificador ODBC
Os aplicativos são responsáveis por criar objetos de evento Win32 usando as funções Win32 correspondentes. Um aplicativo pode associar um identificador de evento Win32 com um identificador de conexão ODBC ou um identificador de instrução ODBC.
Os atributos de conexão SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE e SQL_ATTR_ASYNC_DBC_EVENT determinam se o ODBC é executado no modo assíncrono e se ele habilita o modo de notificação para um identificador de conexão. Os atributos de instrução SQL_ATTR_ASYNC_ENABLE e SQL_ATTR_ASYNC_STMT_EVENT determinam se o ODBC é executado no modo assíncrono e se ele habilita o modo de notificação para um identificador de instrução.
SQL_ATTR_ASYNC_ENABLE ou SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE | SQL_ATTR_ASYNC_STMT_EVENT ou SQL_ATTR_ASYNC_DBC_EVENT | Modo |
---|---|---|
Habilitar | não nulo | Notificações assíncronas |
Habilitar | nulo | Sondagem assíncrona |
Desabilitar | qualquer | Síncrono |
Um aplicativo pode desabilitar temporariamente o modo de operação assíncrona. O ODBC ignora os valores de SQL_ATTR_ASYNC_DBC_EVENT se a operação assíncrona no nível de conexão estiver desabilitada. O ODBC ignora os valores de SQL_ATTR_ASYNC_STMT_EVENT se a operação assíncrona no nível da instrução estiver desabilitada.
Chamada síncrona de SQLSetStmtAttr e SQLSetConnectAttr
O SQLSetConnectAttr é compatível com operações assíncronas, mas a invocação de SQLSetConnectAttr para definir SQL_ATTR_ASYNC_DBC_EVENT é sempre síncrona.
O SQLSetStmtAttr não é compatível com a execução assíncrona.
Cenário de falha
Quando SQLSetConnectAttr é chamado antes de fazer uma conexão, o Gerenciador de Driver não consegue determinar qual driver usar. Portanto, o Gerenciador de Driver retorna êxito para SQLSetConnectAttr mas o atributo pode não estar pronto para ser definido no driver. O Gerenciador de Driver definirá esses atributos quando o aplicativo chamar uma função de conexão. O Gerenciador de Driver pode falhar porque o driver não é compatível com operações assíncronas.
Herança de atributos de conexão
Normalmente, as instruções de uma conexão herdarão os atributos de conexão. No entanto, o atributo SQL_ATTR_ASYNC_DBC_EVENT não é herdado e afeta apenas as operações de conexão.
Para associar um identificador de evento com um identificador de conexão ODBC, um aplicativo ODBC chama a API SQLSetConnectAttr do ODBC e especifica SQL_ATTR_ASYNC_DBC_EVENT como o atributo e o identificador de evento como o valor do atributo. O novo atributo ODBC SQL_ATTR_ASYNC_DBC_EVENT é do tipo SQL_IS_POINTER.
HANDLE hEvent;
hEvent = CreateEvent(
NULL, // default security attributes
FALSE, // auto-reset event
FALSE, // initial state is non-signaled
NULL // no name
);
Normalmente, os aplicativos criam objetos de evento de redefinição automática. O ODBC não redefinirá o objeto de evento. Os aplicativos devem se certificar que o objeto não está no estado de sinalização antes de chamar uma função assíncrona do ODBC.
SQLRETURN retcode;
retcode = SQLSetConnectAttr ( hDBC,
SQL_ATTR_ASYNC_DBC_EVENT, // Attribute name
(SQLPOINTER) hEvent, // Win32 Event handle
SQL_IS_POINTER); // Length Indicator
O SQL_ATTR_ASYNC_DBC_EVENT é um atributo somente do Gerenciador de Driver que não é definido no driver.
O valor padrão de SQL_ATTR_ASYNC_DBC_EVENT é NULL. Se o driver não for compatível com notificações assíncronas, obter ou configurar SQL_ATTR_ASYNC_DBC_EVENT retornará SQL_ERROR com SQLSTATE HY092 (Atributo/identificador de opção inválido).
Se o último valor de SQL_ATTR_ASYNC_DBC_EVENT definido em um identificador de conexão ODBC não for NULL e o aplicativo habilitar o modo assíncrono definindo o atributo SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE com SQL_ASYNC_DBC_ENABLE_ON, chamar qualquer função de conexão ODBC que ofereça suporte ao modo assíncrono retornará uma notificação de conclusão. Se o último valor de SQL_ATTR_ASYNC_DBC_EVENT definido em um identificador de conexão ODBC for NULL, o ODBC não enviará nenhuma notificação ao aplicativo, independentemente de o modo assíncrono estar habilitado.
Um aplicativo pode definir SQL_ATTR_ASYNC_DBC_EVENT antes ou depois de definir o atributo SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE.
Os aplicativos podem definir o atributo SQL_ATTR_ASYNC_DBC_EVENT em um identificador de conexão ODBC antes de chamar uma função de conexão (SQLConnect, SQLBrowseConnect ou SQLDriverConnect). Como o Gerenciador de Driver ODBC não sabe qual driver ODBC o aplicativo usará, ele retornará SQL_SUCCESS. Quando o aplicativo chama uma função de conexão, o Gerenciador de Driver ODBC verificará se o driver oferece suporte a notificações assíncronas. Se o driver não oferecer suporte a notificações assíncronas, o Gerenciador de Driver ODBC retornará SQL_ERROR com SQLSTATE S1_118 (Driver não compatível com notificações assíncronas). Se o driver oferecer suporte a notificações assíncronas, o Gerenciador de Driver ODBC chamará o driver e definirá os atributos correspondentes SQL_ATTR_ASYNC_DBC_NOTIFICATION_CALLBACK e SQL_ATTR_ASYNC_DBC_NOTIFICATION_CONTEXT.
Da mesma forma, um aplicativo chama SQLSetStmtAttr em um identificador de instrução ODBC e especifica o atributo SQL_ATTR_ASYNC_STMT_EVENT para habilitar ou desabilitar a notificação assíncrona no nível da instrução. Como uma função de instrução sempre é chamada depois que uma conexão é estabelecida, SQLSetStmtAttr imediatamente retornará SQL_ERROR com SQLSTATE S1_118 (Driver não compatível com notificações assíncronas) se o driver correspondente não oferecer suporte a operações assíncronas ou se o driver oferecer suporte a operações assíncronas, mas não oferecer suporte a notificações assíncronas.
SQLRETURN retcode;
retcode = SQLSetStmtAttr ( hSTMT,
SQL_ATTR_ASYNC_STMT_EVENT, // Attribute name
(SQLPOINTER) hEvent, // Win32 Event handle
SQL_IS_POINTER); // length Indicator
O SQL_ATTR_ASYNC_STMT_EVENT, que pode ser definido como NULL, é um atributo específico do Gerenciador de Driver que não será definido no driver.
O valor padrão de SQL_ATTR_ASYNC_STMT_EVENT é NULL. Se o driver não for compatível com notificações assíncronas, obter ou configurar SQL_ATTR_ASYNC_STMT_EVENT retornará SQL_ERROR com SQLSTATE HY092 (Atributo/identificador de opção inválido).
Um aplicativo não deve associar o mesmo identificador de evento a mais de um identificador ODBC. Se isso ocorrer, uma notificação será perdida se duas chamadas de função ODBC assíncronas forem concluídas em dois identificadores que compartilham o mesmo identificador de evento. Para evitar que um identificador de instrução herde o mesmo identificador de evento do identificador de conexão, o ODBC retornará SQL_ERROR com SQLSTATE IM016 (Não foi possível definir o atributo de instrução no identificador de conexão) se um aplicativo definir SQL_ATTR_ASYNC_STMT_EVENT em um identificador de conexão.
Chamar funções ODBC assíncronas
Depois de habilitar as notificações assíncronas e iniciar uma operação assíncrona, o aplicativo pode chamar qualquer função ODBC. Se a função pertencer ao conjunto de funções que oferecem suporte à operação assíncrona, o aplicativo receberá uma notificação de conclusão quando a operação for concluída, independentemente de a função ter falhado ou sido bem-sucedida. A única exceção é que o aplicativo chama uma função ODBC com uma conexão ou identificador de instrução inválidos. Nesse caso, o ODBC não obterá o identificador de evento e o definirá para o estado sinalizado.
O aplicativo deve garantir que o objeto de evento associado está em um estado não sinalizado antes de iniciar a operação assíncrona no identificador ODBC correspondente. O ODBC não redefinirá o objeto de evento.
Obter notificações do ODBC
Um thread de aplicativo pode chamar WaitForSingleObject para aguardar em um identificador de evento ou chamar WaitForMultipleObjects para aguardar em uma matriz de identificadores de evento e ser suspenso até que um ou todos os objetos de evento sejam sinalizados ou o intervalo de tempo limite decorra.
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.
}