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 do aplicativo execute outras tarefas. No SDK do Windows 7, para operações de conexão ou instruções assíncronas, um aplicativo determinou que a operação assíncrona havia sido concluída usando o método de sondagem. Para obter mais informações, consulte Execução Assíncrona (Método de Sondagem). A partir do SDK do Windows 8, você pode determinar que 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 desejar o status da operação. O método de notificação é similar ao retorno de chamada e espera no ADO.NET. O ODBC, no entanto, usa eventos Win32 como o objeto de notificação.
A Biblioteca de Cursores ODBC e a notificação assíncrona ODBC não podem ser usadas ao mesmo tempo. Definir ambos os atributos retornará um erro com SQLSTATE S1119 (a Biblioteca de Cursores e a Notificação Assíncrona não podem ser habilitadas ao mesmo tempo).
Confira Notificação de Conclusão de Função Assíncrona para obter informações para desenvolvedores de drivers.
Nota
Não há suporte para o método de notificação com a biblioteca de cursores. Um aplicativo receberá uma mensagem de erro se tentar habilitar a biblioteca de cursores por meio do SQLSetConnectAttr, quando o método de notificação estiver habilitado.
Visão geral
Quando uma função ODBC é chamada no modo assíncrono, o controle é retornado ao aplicativo de chamada imediatamente com o código de retorno SQL_STILL_EXECUTING. O aplicativo deve sondar repetidamente a função até retornar algo diferente de SQL_STILL_EXECUTING. O loop de sondagem aumenta a utilização da CPU, causando um desempenho ruim 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 função SQLCompleteAsync para concluir 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á SQL_ERROR com o SQLSTATE IM017 (a sondagem está 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 instrução ou operação de conexão. Se a solicitação de cancelamento for bem-sucedida, o ODBC retornará SQL_SUCCESS. Esta mensagem não indica que a função foi realmente cancelada; indica que a solicitação de cancelamento foi processada. Se a função é realmente cancelada é dependente do driver e dependente da fonte de dados. Quando uma operação for cancelada, o Gerenciador de Driver ainda sinalizará o evento. O Gerenciador de Driver retorna SQL_ERROR no buffer de código de retorno e o estado é SQLSTATE HY008 (Operação cancelada) para indicar que o cancelamento foi bem-sucedido. Se a função tiver concluído seu processamento normal, o Gerenciador de Driver retornará SQL_SUCCESS ou SQL_SUCCESS_WITH_INFO.
Comportamento de nível inferior
A versão do Gerenciador de Driver ODBC que dá suporte a essa notificação completa é ODBC 3.81.
Versão ODBC do aplicativo | Versão do Gerenciador de Driver | Versão do driver | Comportamento |
---|---|---|---|
Novo aplicativo de qualquer versão do ODBC | ODBC 3.81 | Driver ODBC 3.80 | O aplicativo poderá usar esse recurso se o driver der suporte a esse recurso, caso contrário, o Gerenciador de Driver apresentará um erro. |
Novo aplicativo para qualquer versão do ODBC | ODBC 3.81 | Driver anterior à versão ODBC 3.80 | O Gerenciador de Driver apresentará um erro se o driver não der suporte a esse recurso. |
Novo aplicativo de qualquer versão do ODBC | Versão anterior à ODBC 3.81 | Qualquer | Quando o aplicativo usa esse recurso, um Gerenciador de Driver Antigo 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 apresentar erros e a versão do Gerenciador de Drivers for anterior à versão ODBC 3.81, o comportamento será indefinido.
Casos de uso
Esta seção mostra casos de uso para execução assíncrona e o mecanismo de sondagem.
Integrar dados de várias fontes ODBC
Um aplicativo de integração de dados busca de forma assíncrona dados de várias fontes de dados. Alguns dos dados são de fontes de dados remotas e alguns dados são de arquivos locais. O aplicativo não pode continuar até que as operações assíncronas sejam concluídas.
Em vez de sondar 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 aguardar um objeto de evento ou muitos objetos de evento (eventos ODBC e outros eventos do Windows). O ODBC sinalizará o objeto de evento quando a operação assíncrona ODBC correspondente for concluída.
No Windows, os objetos de evento Win32 serão usados e fornecerão ao usuário um modelo de programação unificado. Os Gerenciadores de Driver em outras plataformas podem usar a implementação específica do objeto de evento dessas plataformas.
O seguinte exemplo de código demonstra o uso de notificações assíncronas tanto para conexão quanto para 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 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;
}
Determinando se um driver dá suporte à notificação assíncrona
Um aplicativo ODBC pode determinar se um driver ODBC dá suporte à notificação assíncrona chamando SQLGetInfo. Assim, o Gerenciador de Driver ODBC chamará 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
}
}
Associando um identificador de evento Win32 a 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 a um identificador de conexão ODBC ou a 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 em modo assíncrono e se o ODBC 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 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ção assíncrona |
Habilitar | nulo | Sondagem assíncrona |
Desabilitar | qualquer | Síncrono |
Um aplicativo pode desabilitar temporalmente o modo de operação assíncrona. O ODBC ignora os valores de SQL_ATTR_ASYNC_DBC_EVENT se a operação assíncrona de nível de conexão estiver desabilitada. O ODBC ignorará os valores de SQL_ATTR_ASYNC_STMT_EVENT se a operação assíncrona de nível de instrução estiver desabilitada.
Chamada síncrona de SQLSetStmtAttr e SQLSetConnectAttr
SQLSetConnectAttr dá suporte a operações assíncronas, mas a invocação de SQLSetConnectAttr para definir SQL_ATTR_ASYNC_DBC_EVENT é sempre síncrona.
SQLSetStmtAttr não dá suporte à execução assíncrona.
Cenário de interrupção devido a erro
Quando SQLSetConnectAttr é chamado antes de fazer uma conexão, o Gerenciador de Driver não pode 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 dá suporte a operações assíncronas.
Herança de atributos de conexão
Normalmente, as declarações de uma conexão herdarão os atributos de conexão. No entanto, o atributo SQL_ATTR_ASYNC_DBC_EVENT não é herdável e afeta apenas as operações de conexão.
Para associar um identificador de evento a um identificador de conexão ODBC, um aplicativo ODBC chama a API ODBC SQLSetConnectAttr 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
);
Em geral, os aplicativos criam objetos de evento de redefinição automática. O ODBC não redefinirá o objeto de evento. Os aplicativos devem verificar se o objeto não está em estado sinalizado antes de chamar qualquer função ODBC assíncrona.
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 é um atributo somente do Gerenciador de Driver que não será definido no driver.
O valor padrão de SQL_ATTR_ASYNC_DBC_EVENT é NULL. Se o driver não der suporte à notificação assíncrona, obter ou definir SQL_ATTR_ASYNC_DBC_EVENT retornará SQL_ERROR com SQLSTATE HY092 (identificador de atributo/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 tiver habilitado o modo assíncrono definindo o atributo SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE para SQL_ASYNC_DBC_ENABLE_ON, a chamada de qualquer função de conexão ODBC que suporte o modo assíncrono resultará em uma notificação de conclusão. Se o valor SQL_ATTR_ASYNC_DBC_EVENT mais recente 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, SQLBrowseConnectou 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 dá suporte à notificação assíncrona. Se o driver não der suporte à notificação assíncrona, o Gerenciador de Driver ODBC retornará SQL_ERROR com S1_118 SQLSTATE (o Driver não dá suporte à notificação assíncrona). Se o driver der suporte à notificação assíncrona, 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 declaração é sempre chamada depois que a conexão é estabelecida, SQLSetStmtAttr retornará SQL_ERROR com SQLSTATE S1_118 (o Driver não oferece suporte à notificação assíncrona) imediatamente 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 à notificação assíncrona.
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, que pode ser definido como NULL, é um atributo somente 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 der suporte à notificação assíncrona, obter ou definir o atributo SQL_ATTR_ASYNC_ STMT_EVENT retornará SQL_ERROR com SQLSTATE HY092 (identificador de atributo/opção inválido).
Um aplicativo não deve associar o mesmo identificador de evento a mais de um identificador ODBC. Caso contrário, uma notificação será perdida se duas invocações de função ODBC assíncrona forem concluídas em dois identificadores que compartilham o mesmo identificador de evento. Para evitar que um identificador de instrução herda o mesmo identificador de evento do identificador de conexão, o ODBC retorna SQL_ERROR com o SQLSTATE IM016 (não é 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.
Chamando funções ODBC assíncronas
Depois de habilitar a notificação assíncrona 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 dão 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álido. Nesse caso, o ODBC não obterá o identificador de evento e o definirá no estado sinalizado.
O aplicativo deve garantir que o objeto de evento associado esteja em um estado não sinalizado antes de iniciar uma operação assíncrona no identificador ODBC correspondente. O ODBC não redefinirá o objeto de evento.
Recebendo notificação do ODBC
Um thread de aplicativo pode chamar WaitForSingleObject para aguardar um identificador de evento ou chamar WaitForMultipleObjects para aguardar uma matriz de identificadores de evento, ficando suspenso até a sinalização de um ou todos os objetos de evento ou o fim do intervalo de tempo limite.
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.
}