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 instrução assíncrona ou operações de conexão, um aplicativo determinou que a operação assíncrona foi 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 foi 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. 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. A configuração de ambos os atributos retornará um erro com SQLSTATE S1119 (Biblioteca de Cursores e Notificação Assíncrona não podem ser habilitadas ao mesmo tempo).
Consulte "Notificação de Conclusão de Função Assíncrona" para informações destinadas a desenvolvedores de drivers.
Observação
O método de notificação não é suportado com a biblioteca de cursores. Um aplicativo receberá uma mensagem de erro se tentar habilitar a biblioteca de cursores via 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é 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 é desativado. 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 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 o pedido de cancelamento foi processado. Se a função é realmente cancelada é dependente 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 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 concluiu seu processamento normal, o Gerenciador de Driver retorna SQL_SUCCESS ou SQL_SUCCESS_WITH_INFO.
Comportamento de nível inferior
A versão do ODBC Driver Manager que suporta esta notificação quando concluída é ODBC 3.81.
Versão ODBC do aplicativo | Versão do Driver Manager | Versão do driver | Comportamento |
---|---|---|---|
Nova aplicação de qualquer versão ODBC | ODBC 3,81 | ODBC 3.80 Driver | A aplicação pode usar esse recurso se o driver o suportar, caso contrário, o gestor de drivers irá apresentar um erro. |
Nova aplicação de qualquer versão ODBC | ODBC 3,81 | Driver pré-ODBC 3.80 | O Gestor de Driver irá apresentar um erro se o driver não suportar esta funcionalidade. |
Nova aplicação de qualquer versão ODBC | Pré-ODBC 3.81 | Qualquer | Quando a aplicação utiliza este recurso, um Gestor de Drivers antigo considerará os novos atributos como atributos específicos do driver, e o driver deve apresentar um erro. Um novo Gestor de Drivers não passará esses atributos para o driver. |
Um aplicativo deve verificar a versão do Driver Manager antes de usar esse recurso. Caso contrário, se um driver mal escrito não erro e a versão do Gerenciador de Driver é pré ODBC 3.81, o comportamento é 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 dados de forma assíncrona 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 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, objetos de evento Win32 serão usados e que fornecerão ao usuário um modelo de programação unificado. Os gerentes de driver em outras plataformas podem usar a implementação do objeto de evento específico para essas plataformas.
O exemplo de código a seguir demonstra o uso de notificação assíncrona de declaração e conexã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 oferece suporte a notificação assíncrona
Um aplicativo ODBC pode determinar se um driver ODBC oferece suporte a notificação assíncrona chamando SQLGetInfo. O Gestor de Drivers ODBC, assim, 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
}
}
Associando um identificador de evento Win32 a um identificador ODBC
Os aplicativos são responsáveis pela criação de 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 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 o ODBC 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 |
---|---|---|
Ativar | não-nulo | Notificação assíncrona |
Ativar | nulo | Sondagem assíncrona |
Desativar | qualquer | Síncrono |
Um aplicativo pode desativar temporariamente o modo de operação assíncrona. O ODBC ignora valores de SQL_ATTR_ASYNC_DBC_EVENT se a operação assíncrona de nível de conexão estiver desabilitada. ODBC ignora valores de SQL_ATTR_ASYNC_STMT_EVENT se a operação assíncrona de nível de instrução estiver desabilitada.
Chamada síncrona do SQLSetStmtAttr e SQLSetConnectAttr
SQLSetConnectAttr suporta operações assíncronas, mas a invocação de SQLSetConnectAttr para definir SQL_ATTR_ASYNC_DBC_EVENT é sempre síncrona.
SQLSetStmtAttr não suporta execução assíncrona.
Cenário de interrupção por 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 definir no driver. O Gerenciador de Driver definirá esses atributos quando o aplicativo chamar uma função de conexão. O Gestor de Drivers pode apresentar um erro porque o driver não suporta operações assíncronas.
Herança de atributos de conexão
Normalmente, as declarações de uma conexão herdam os atributos dessa conexão. No entanto, o atributo SQL_ATTR_ASYNC_DBC_EVENT não é hereditário 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
);
Normalmente, os aplicativos criam objetos de evento de redefinição automática. ODBC não redefinirá o objeto de evento. Os aplicativos devem certificar-se de que o objeto não está no 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 exclusivo 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 oferecer suporte a notificações assíncronas, a obtenção ou definição do atributo SQL_ATTR_ASYNC_DBC_EVENT resultará em SQL_ERROR com o SQLSTATE HY092 (Identificador de atributo/opção inválido).
Se o valor da última SQL_ATTR_ASYNC_DBC_EVENT definido num identificador de ligação ODBC não for NULL e o aplicativo ativar o modo assíncrono definindo o atributo SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE para SQL_ASYNC_DBC_ENABLE_ON, chamar qualquer função de ligação ODBC que suporte o modo assíncrono receberá uma notificação de conclusão. Se o valor da última SQL_ATTR_ASYNC_DBC_EVENT definido num identificador de ligação ODBC for NULL, o ODBC não enviará nenhuma notificação à aplicação, independentemente de o modo assíncrono estar ativado.
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 suporta notificação assíncrona. Se o driver não oferecer suporte a notificação assíncrona, o Gerenciador de Driver ODBC retornará SQL_ERROR com S1_118 SQLSTATE (Driver não suporta notificação assíncrona). Se o driver oferecer 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 instrução é sempre chamada depois que a conexão é estabelecida, SQLSetStmtAttr retornará SQL_ERROR com S1_118 SQLSTATE (Driver não suporta notificação assíncrona) imediatamente se o driver correspondente não oferecer suporte a operações assíncronas ou se o driver suportar operação assíncrona, mas não suportar 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 exclusivo do Gestor de Drivers que não é aplicado no driver.
O valor padrão de SQL_ATTR_ASYNC_STMT_EVENT é NULL. Se o driver não oferecer 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í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 é 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 suportam a 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 a aplicação chama uma função ODBC com uma conexão inválida ou um identificador de instrução inválido. Nesse caso, o ODBC não obterá o identificador de evento e colocá-lo-á 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. ODBC não redefinirá o objeto de evento.
Obtendo notificação do ODBC
Um thread de aplicação pode chamar WaitForSingleObject para aguardar num identificador de evento ou chamar WaitForMultipleObjects para aguardar numa 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 decorre.
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.
}