Asynchronní spuštění (metoda oznámení)
Rozhraní ODBC umožňuje asynchronní spouštění operací připojení a příkazů. Vlákno aplikace může volat funkci ODBC v asynchronním režimu a funkce se může vrátit před dokončením operace, což umožňuje vláknu aplikace provádět další úlohy. Ve Windows 7 SDK pro asynchronní operace příkazů nebo připojení aplikace pomocí metody dotazování zjistila, že asynchronní operace byla dokončena. Další informace najdete v Asynchronní provedení (Metoda dotazování). Počínaje sadou Windows 8 SDK můžete určit, že asynchronní operace je dokončena pomocí metody oznámení.
V metodě dotazování musí aplikace volat asynchronní funkci pokaždé, když chce stav operace. Metoda oznámení je podobná zpětnému volání a čekání v ADO.NET. Odbc však jako objekt oznámení používá události Win32.
Knihovna kurzorů ODBC a asynchronní oznámení ODBC nelze použít současně. Nastavení obou atributů vrátí chybu s SQLSTATE S1119 (Knihovna kurzorů a asynchronní oznámení nelze povolit současně).
Informace pro vývojáře ovladačů najdete v oznámení o dokončení asynchronní funkce.
Poznámka
Metoda oznámení není podporována v knihovně kurzorů. Aplikace obdrží chybovou zprávu, pokud se pokusí povolit knihovnu kurzoru prostřednictvím SQLSetConnectAttr, když je povolena metoda oznámení.
Přehled
Pokud je funkce ODBC volána v asynchronním režimu, ovládací prvek se vrátí volající aplikaci okamžitě s návratovým kódem SQL_STILL_EXECUTING. Aplikace musí opakovaně dotazovat funkci, dokud nevrátí něco jiného než SQL_STILL_EXECUTING. Smyčka dotazování zvyšuje využití procesoru, což způsobuje nízký výkon v mnoha asynchronních scénářích.
Pokaždé, když se použije model oznámení, je model dotazování zakázaný. Aplikace by neměly znovu volat původní funkci. Volání funkce SQLCompleteAsync dokončení asynchronní operace. Pokud aplikace znovu volá původní funkci před dokončením asynchronní operace, volání vrátí SQL_ERROR s SQLSTATE IM017 (dotazování je zakázáno v asynchronním režimu oznámení).
Při použití modelu oznámení může aplikace volat SQLCancel nebo SQLCancelHandle zrušit příkaz nebo operaci připojení. Pokud je žádost o zrušení úspěšná, vrátí rozhraní ODBC SQL_SUCCESS. Tato zpráva neznačí, že funkce byla skutečně zrušena; označuje, že žádost o zrušení byla zpracována. Zda je funkce ve skutečnosti zrušena, je závislá na ovladači a na zdroji dat závislá. Když je operace zrušena, správce ovladačů bude i nadále signalizovat událost. Správce ovladačů vrátí SQL_ERROR ve vyrovnávací paměti návratového kódu a stav je nastaven na SQLSTATE HY008 (operace „zrušena“), což označuje, že zrušení proběhlo úspěšně. Pokud funkce dokončila normální zpracování, správce ovladačů vrátí SQL_SUCCESS nebo SQL_SUCCESS_WITH_INFO.
Chování nižší úrovně
Verze Správce ovladačů ODBC podporující toto oznámení je ODBC 3.81.
Verze ODBC aplikace | Verze Správce ovladačů | Verze ovladače | Chování |
---|---|---|---|
Nová aplikace jakékoli verze ODBC | ODBC 3.81 | Ovladač ODBC 3.80 | Aplikace může tuto funkci použít, pokud ji ovladač podporuje, jinak správce ovladačů vyvolá chybu. |
Nová aplikace jakékoli verze ODBC | ODBC 3.81 | Ovladač Pre-ODBC 3.80 | Správci ovladačů nastane chyba, jestliže ovladač tuto funkci nepodporuje. |
Nová aplikace jakékoli verze ODBC | Před ODBC 3.81 | Jakýkoliv | Když aplikace používá tuto funkci, starý Správce ovladačů bude považovat nové atributy za atributy specifické pro ovladače a ovladač by měl vyvolat chybu. Nový Správce ovladačů tyto atributy ovladači nepředá. |
Před použitím této funkce by aplikace měla zkontrolovat verzi Správce ovladačů. V opačném případě, pokud špatně napsaný ovladač nezaznamená chybu a verze Správce ovladačů je před verzí ODBC 3.81, chování není definováno.
Případy použití
Tato část ukazuje příklady použití pro asynchronní spuštění a mechanismus pollingu.
Integrace dat z více zdrojů ODBC
Aplikace pro integraci dat asynchronně načítá data z více zdrojů dat. Některá data pocházejí ze vzdálených zdrojů dat a některá data pocházejí z místních souborů. Aplikace nemůže pokračovat, dokud nebudou dokončeny asynchronní operace.
Místo opakovaného dotazování operace, která určí, jestli je dokončená, může aplikace vytvořit objekt události a přidružit ho k popisovači připojení ODBC nebo k popisovači příkazu ODBC. Aplikace pak volá rozhraní API pro synchronizaci operačního systému, aby čekala na jeden objekt události nebo na mnoho objektů událostí (události ODBC i jiné události Systému Windows). Rozhraní ODBC bude signalizovat objekt události při dokončení odpovídající asynchronní operace ODBC.
Ve Windows se použijí objekty událostí Win32, které uživatelům poskytnou jednotný programovací model. Správci ovladačů na jiných platformách mohou použít implementaci objektu události specifickou pro tyto platformy.
Následující ukázka kódu ukazuje použití asynchronní notifikace pro připojení a dotaz:
// 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;
}
Určení, jestli ovladač podporuje asynchronní oznámení
Aplikace ODBC může určit, zda ovladač ODBC podporuje asynchronní oznámení voláním SQLGetInfo. Správce ovladačů ODBC bude následně volat SQLGetInfo ovladače s 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
}
}
Přidružení popisovače události Win32 k popisovači ODBC
Aplikace zodpovídají za vytváření objektů událostí Win32 pomocí odpovídajících funkcí Win32. Aplikace může přidružit jeden popisovač události Win32 k jednomu popisovači připojení ODBC nebo jednomu popisovači příkazu ODBC.
Atributy připojení SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE a SQL_ATTR_ASYNC_DBC_EVENT určují, jestli se rozhraní ODBC spouští v asynchronním režimu a jestli rozhraní ODBC povoluje režim oznámení pro popisovač připojení. Atributy příkazů SQL_ATTR_ASYNC_ENABLE a SQL_ATTR_ASYNC_STMT_EVENT určují, jestli se rozhraní ODBC spouští v asynchronním režimu a zda odbc povoluje režim oznámení pro popisovač příkazu.
SQL_ATTR_ASYNC_ENABLE nebo SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE | SQL_ATTR_ASYNC_STMT_EVENT nebo SQL_ATTR_ASYNC_DBC_EVENT | Režim |
---|---|---|
Zapnout | non-null | Asynchronní oznámení |
Zapnout | nula | Asynchronní dotazování |
Vypnout | jakýkoliv | Synchronní |
Aplikace může časově zakázat asynchronní režim operace. Rozhraní ODBC ignoruje hodnoty SQL_ATTR_ASYNC_DBC_EVENT, pokud je deaktivována asynchronní operace na úrovni připojení. Rozhraní ODBC ignoruje hodnoty SQL_ATTR_ASYNC_STMT_EVENT pokud je asynchronní operace na úrovni příkazu zakázaná.
Synchronní volání SQLSetStmtAttr a SQLSetConnectAttr
SQLSetConnectAttr podporuje asynchronní operace, ale vyvolání sqlSetConnectAttr k nastavení SQL_ATTR_ASYNC_DBC_EVENT je vždy synchronní.
sqlSetStmtAttr nepodporuje asynchronní spouštění.
Scénář selhání kvůli chybě
Když je SQLSetConnectAttr volána před vytvořením připojení, nemůže Správce ovladačů určit, jaký ovladač má použít. Správce ovladačů proto vrátí úspěch pro SQLSetConnectAttr, ale atribut nemusí být připravený k nastavení v ovladači. Správce ovladačů nastaví tyto atributy, když aplikace volá funkci připojení. Správce ovladačů může vyvolat chybu, protože ovladač nepodporuje asynchronní operace.
Dědičnost atributů připojení
Příkazy připojení obvykle dědí atributy připojení. Atribut SQL_ATTR_ASYNC_DBC_EVENT však není zděděný a ovlivňuje pouze operace připojení.
Chcete-li přidružit popisovač události k popisovači připojení ODBC, aplikace ODBC volá rozhraní API ODBC SQLSetConnectAttr a určuje SQL_ATTR_ASYNC_DBC_EVENT jako atribut a popisovač události jako hodnotu atributu. Nový atribut ODBC SQL_ATTR_ASYNC_DBC_EVENT je typu SQL_IS_POINTER.
HANDLE hEvent;
hEvent = CreateEvent(
NULL, // default security attributes
FALSE, // auto-reset event
FALSE, // initial state is non-signaled
NULL // no name
);
Aplikace obvykle vytvářejí objekty událostí automatického resetování. Rozhraní ODBC neobnoví objekt události. Aplikace musí před voláním jakékoli asynchronní funkce ODBC zajistit, aby objekt nebyl v signalizačním stavu.
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 je atribut pouze správce ovladačů, který nebude nastaven v ovladači.
Výchozí hodnota SQL_ATTR_ASYNC_DBC_EVENT je NULL. Pokud ovladač nepodporuje asynchronní oznámení, získání nebo nastavení SQL_ATTR_ASYNC_DBC_EVENT vrátí SQL_ERROR s SQLSTATE HY092 (neplatný identifikátor atributu nebo možnosti).
Pokud poslední hodnota SQL_ATTR_ASYNC_DBC_EVENT nastavená na popisovači připojení ODBC není NULL a aplikace povolila asynchronní režim nastavením atributu SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE s SQL_ASYNC_DBC_ENABLE_ON, volání jakékoliv funkce připojení ODBC, která podporuje asynchronní režim, obdrží oznámení o dokončení. Pokud poslední SQL_ATTR_ASYNC_DBC_EVENT hodnota nastavená na popisovač připojení ODBC má hodnotu NULL, rozhraní ODBC neodesílá aplikaci žádné oznámení bez ohledu na to, zda je povolený asynchronní režim.
Aplikace může nastavit SQL_ATTR_ASYNC_DBC_EVENT před nebo po nastavení SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE atributu.
Aplikace mohou nastavit atribut SQL_ATTR_ASYNC_DBC_EVENT na popisovač připojení ODBC před voláním funkce připojení (SQLConnect, SQLBrowseConnectnebo SQLDriverConnect). Protože správce ovladačů ODBC neví, který ovladač ODBC bude aplikace používat, vrátí SQL_SUCCESS. Když aplikace volá funkci připojení, správce ovladačů ODBC zkontroluje, jestli ovladač podporuje asynchronní oznámení. Pokud ovladač nepodporuje asynchronní oznámení, správce ovladačů ODBC vrátí SQL_ERROR s S1_118 SQLSTATE (ovladač nepodporuje asynchronní oznámení). Pokud ovladač podporuje asynchronní oznámení, správce ovladačů ODBC zavolá ovladač a nastaví odpovídající atributy SQL_ATTR_ASYNC_DBC_NOTIFICATION_CALLBACK a SQL_ATTR_ASYNC_DBC_NOTIFICATION_CONTEXT.
Podobně, aplikace volá SQLSetStmtAttr na popisovači příkazu ODBC a určuje atribut SQL_ATTR_ASYNC_STMT_EVENT k povolení nebo zákazu asynchronního oznámení na úrovni příkazu. Vzhledem k tomu, že funkce příkazu je vždy volána po navázání připojení, SQLSetStmtAttr vrátí SQL_ERROR s SQLSTATE S1_118 (ovladač nepodporuje asynchronní oznámení) okamžitě, pokud odpovídající ovladač nepodporuje asynchronní operace nebo ovladač podporuje asynchronní operace, ale nepodporuje asynchronní oznámení.
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, který lze nastavit na HODNOTU NULL, je atribut správce ovladačů, který nebude nastaven v ovladači.
Výchozí hodnota SQL_ATTR_ASYNC_STMT_EVENT má hodnotu NULL. Pokud ovladač nepodporuje asynchronní oznámení, získání nebo nastavení atributu SQL_ATTR_ASYNC_ STMT_EVENT vrátí SQL_ERROR s SQLSTATE HY092 (neplatný identifikátor atributu nebo možnosti).
Aplikace by neměla přidružit stejný popisovač události k více než jednomu popisovači ODBC. Jinak dojde ke ztrátě jednoho oznámení, pokud se dvě asynchronní vyvolání funkce ODBC dokončí na dvou popisovačích, které sdílejí stejný popisovač události. Aby se zabránilo tomu, že popisovač příkazu zdědí stejný popisovač události z popisovače připojení, vrátí ODBC chybový kód SQL_ERROR s SQLSTATE IM016 (Nelze nastavit atribut příkazu do popisovače připojení), pokud aplikace nastaví SQL_ATTR_ASYNC_STMT_EVENT na popisovač připojení.
Volání asynchronních funkcí ODBC
Po povolení asynchronního oznámení a spuštění asynchronní operace může aplikace volat libovolnou funkci ODBC. Pokud funkce patří do sady funkcí, které podporují asynchronní operaci, aplikace obdrží oznámení o dokončení po dokončení operace bez ohledu na to, jestli funkce selhala nebo byla úspěšná. Jedinou výjimkou je, že aplikace volá funkci ODBC s neplatným připojením nebo popisovačem příkazu. V tomto případě rozhraní ODBC nezíská popisovač události a nenastaví ho do signalizovaného stavu.
Aplikace musí zajistit, aby přidružený objekt události byl v nesignálním stavu, před spuštěním asynchronní operace na odpovídajícím popisovači ODBC. Rozhraní ODBC neobnoví objekt události.
Získání oznámení z rozhraní ODBC
Vlákno aplikace může volat WaitForSingleObject, aby čekalo na jeden popisovač události, nebo volat WaitForMultipleObjects, aby čekalo na pole popisovačů událostí a být pozastaveno, dokud není některý nebo všechny objekt(y) události signalizován(y) nebo neuplyne časový limit.
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.
}