非同期実行 (通知方法)
ODBC を使用すると、接続操作とステートメント操作を非同期で実行できます。 アプリケーション スレッドは非同期モードで ODBC 関数を呼び出すことができます。また、操作が完了する前に関数が戻り、アプリケーション スレッドが他のタスクを実行できるようになります。 Windows 7 SDK では、非同期ステートメントまたは接続操作の場合、アプリケーションはポーリング メソッドを使用して非同期操作が完了したと判断しました。 詳細については、「非同期実行 (ポーリング メソッド)」を参照してください。 Windows 8 SDK 以降では、通知メソッドを使用して非同期操作が完了したことを確認できます。
ポーリング メソッドでは、アプリケーションは操作の状態が必要なたびに非同期関数を呼び出す必要があります。 通知メソッドはコールバックに似ていて、ADO.NET で待機します。 ただし、ODBC では、Win32 イベントが通知オブジェクトとして使用されます。
ODBC カーソル ライブラリと ODBC 非同期通知を同時に使用することはできません。 両方の属性を設定すると、SQLSTATE S1119 でエラーが返されます (カーソル ライブラリと非同期通知を同時に有効にすることはできません)。
ドライバー開発者向けの詳細については、「非同期関数の完了の通知」を参照してください。
Note
通知メソッドは、カーソル ライブラリではサポートされていません。 アプリケーションは、通知メソッドが有効になっているときに、SQLSetConnectAttr を介してカーソル ライブラリを有効にしようとすると、エラー メッセージを受け取ります。
概要
ODBC 関数が非同期モードで呼び出されると、制御はリターン コード SQL_STILL_EXECUTINGを使用して呼び出し元アプリケーションに直ちに返されます。 アプリケーションは、SQL_STILL_EXECUTING以外のものを返すまで関数を繰り返しポーリングする必要があります。 ポーリング ループにより CPU 使用率が増加し、多くの非同期シナリオでパフォーマンスが低下します。
通知モデルが使用されるたびに、ポーリング モデルは無効になります。 アプリケーションで元の関数を再度呼び出さないでください。 SQLCompleteAsync 関数を呼び出して非同期操作を完了します。 非同期操作が完了する前にアプリケーションが元の関数を再度呼び出すと、呼び出しは SQLSTATE IM017 で SQL_ERRORを返します (ポーリングは非同期通知モードでは無効になっています)。
通知モデルを使用する場合、アプリケーションは SQLCancel または SQLCancelHandle を呼び出して、ステートメントまたは接続操作を取り消すことができます。 キャンセル要求が成功した場合、ODBC は SQL_SUCCESS を返します。 このメッセージは、関数が実際に取り消されたことを示すものではありません。これは、取り消し要求が処理されたことを示します。 関数が実際に取り消されるかどうかは、ドライバーとデータ ソースに依存します。 操作が取り消されると、ドライバー マネージャーは引き続きイベントを通知します。 ドライバー マネージャーはリターン コード バッファー内の SQL_ERROR を返し、状態は SQLSTATE HY008 (操作が取り消されました) で、取り消しが成功したことを示します。 関数が通常の処理を完了した場合、ドライバー マネージャーは SQL_SUCCESS または SQL_SUCCESS_WITH_INFO を返します。
ダウンレベルの動作
完了時にこの通知をサポートする ODBC ドライバー マネージャーのバージョンは ODBC 3.81 です。
アプリケーション ODBC のバージョン | ドライバー マネージャーのバージョン | ドライバー バージョン | Behavior |
---|---|---|---|
任意の 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 を呼び出して、1 つのイベント オブジェクトまたは多数のイベント オブジェクト (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 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;
}
ドライバーが非同期通知をサポートしているかどうかを判断する
ODBC アプリケーションは、SQLGetInfo を呼び出すことによって、ODBC ドライバーが非同期通知をサポートしているかどうかを判断できます。 その結果、ODBC ドライバー マネージャーは、SQL_ASYNC_NOTIFICATIONを 使用してドライバーの SQLGetInfo を呼び出します。
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 イベント オブジェクトを作成します。 アプリケーションは、1 つの Win32 イベント ハンドルを 1 つの ODBC 接続ハンドルまたは 1 つの 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 | モード |
---|---|---|
有効にする | null 以外 | 非同期通知 |
有効にする | null | 非同期ポーリング |
Disable | any | 同期 |
アプリケーションは、非同期操作モードを一時的に無効にすることができます。 ODBC では、接続レベルの非同期操作が無効になっている場合、SQL_ATTR_ASYNC_DBC_EVENT の値は無視されます。 ステートメント レベルの非同期操作が無効になっている場合、ODBC は SQL_ATTR_ASYNC_STMT_EVENT の値を無視します。
SQLSetStmtAttr と SQLSetConnectAttr の同期呼び出し
SQLSetConnectAttr は非同期操作をサポートしますが、SQL_ATTR_ASYNC_DBC_EVENT を設定する SQLSetConnectAttr の呼び出しは常に同期的です。
SQLSetStmtAttr は非同期実行をサポートしていません。
エラーのシナリオ
接続する前に SQLSetConnectAttr が呼び出されると、ドライバー マネージャーは使用するドライバーを判断できません。 そのため、ドライバー マネージャーは SQLSetConnectAttr の成功を返しますが、ドライバーで設定する準備ができていない可能性があります。 ドライバー マネージャーは、アプリケーションが接続関数を呼び出すときにこれらの属性を設定します。 ドライバー マネージャーは、ドライバーが非同期操作をサポートしていないためにエラーが発生する可能性があります。
接続属性の継承
通常、接続のステートメントは接続属性を継承します。 ただし、SQL_ATTR_ASYNC_DBC_EVENT 属性は継承可能ではなく、接続操作にのみ影響します。
イベント ハンドルを ODBC 接続ハンドルに関連付けるために、ODBC アプリケーションは ODBC API 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 を取得または設定すると、SQLSTATE HY092 (無効な属性/オプション識別子) で SQL_ERROR が返されます。
ODBC 接続ハンドルに設定された最後の SQL_ATTR_ASYNC_DBC_EVENT 値が NULL ではなく、SQL_ASYNC_DBC_ENABLE_ON で 属性SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE を設定してアプリケーションが非同期モードを有効にした場合、非同期モードをサポートする ODBC 接続関数を呼び出すと完了通知が表示されます。 ODBC 接続ハンドルに設定された最後の SQL_ATTR_ASYNC_DBC_EVENT 値が NULL の場合、ODBC は非同期モードが有効かどうかに関係なく、アプリケーションに通知を送信しません。
アプリケーションは、属性 SQL_ATTR_ASYNC_DBC_FUNCTION_ENABLE の設定の前後に SQL_ATTR_ASYNC_DBC_EVENT を設定できます。
アプリケーションは、接続関数 (SQLConnect、SQLBrowseConnect、または SQLDriverConnect) を呼び出す前に、ODBC 接続ハンドルに SQL_ATTR_ASYNC_DBC_EVENT 属性を設定できます。 ODBC ドライバー マネージャーは、アプリケーションで使用する ODBC ドライバーを認識しないため、SQL_SUCCESS を返します。 アプリケーションが接続関数を呼び出すと、ODBC ドライバー マネージャーは、ドライバーが非同期通知をサポートしているかどうかを確認します。 ドライバーが非同期通知をサポートしていない場合、ODBC ドライバー マネージャーは SQLSTATE S1_118 で SQL_ERROR を返します (ドライバーは非同期通知をサポートしていません)。 ドライバーが非同期通知をサポートしている場合、ODBC ドライバー マネージャーはドライバーを呼び出し、対応する属性 SQL_ATTR_ASYNC_DBC_NOTIFICATION_CALLBACK および SQL_ATTR_ASYNC_DBC_NOTIFICATION_CONTEXT を設定します。
同様に、アプリケーションは ODBC ステートメント ハンドルで SQLSetStmtAttr を呼び出し、ステートメント レベルの非同期通知を有効または無効にする SQL_ATTR_ASYNC_STMT_EVENT 属性を指定します。 ステートメント関数は接続が確立された後に常に呼び出されるため対応するドライバーが非同期操作をサポートしていない場合、またはドライバーが非同期操作をサポートしているが非同期通知をサポートしていない場合、SQLSetStmtAttr は SQLSTATE S1_118 で SQL_ERROR をすぐに返します (ドライバーは非同期通知をサポートしていません)。
SQLRETURN retcode;
retcode = SQLSetStmtAttr ( hSTMT,
SQL_ATTR_ASYNC_STMT_EVENT, // Attribute name
(SQLPOINTER) hEvent, // Win32 Event handle
SQL_IS_POINTER); // length Indicator
NULL に設定可能な SQL_ATTR_ASYNC_STMT_EVENT は、ドライバーで設定されないドライバー マネージャーのみの属性です。
SQL_ATTR_ASYNC_STMT_EVENT の既定値は NULL です。 ドライバーが非同期通知をサポートしていない場合、SQL_ATTR_ASYNC_ STMT_EVENT 属性を取得または設定すると、SQLSTATE HY092 (無効な属性/オプション識別子) で SQL_ERROR が返されます。
アプリケーションは、同じイベント ハンドルを複数の ODBC ハンドルに関連付けないようにする必要があります。 それ以外の場合、同じイベント ハンドルを共有する 2 つのハンドルで 2 つの非同期 ODBC 関数呼び出しが完了すると、1 つの通知が失われます。 ステートメント ハンドルが接続ハンドルから同じイベント ハンドルを継承しないようにするために、アプリケーションが接続ハンドルに SQL_ATTR_ASYNC_STMT_EVENT を設定した場合、ODBC は SQLSTATE IM016 (ステートメント属性を接続ハンドルに設定できません) で SQL_ERROR を返します。
非同期 ODBC 関数の呼び出し
非同期通知を有効にして非同期操作を開始すると、アプリケーションは任意の ODBC 関数を呼び出すことができます。 関数が非同期操作をサポートする関数のセットに属している場合、関数が失敗したか成功したかに関係なく、操作が完了すると、アプリケーションは完了通知を受け取ります。 唯一の例外は、アプリケーションが無効な接続またはステートメント ハンドルを使用して ODBC 関数を呼び出す点です。 この場合、ODBC はイベント ハンドルを取得せず、シグナル状態に設定します。
アプリケーションは、対応する ODBC ハンドルで非同期操作を開始する前に、関連付けられているイベント オブジェクトが非シグナル状態であることを確認する必要があります。 ODBC はイベント オブジェクトをリセットしません。
ODBC からの通知の取得
アプリケーション スレッドは WaitForSingleObject を呼び出して 1 つのイベント ハンドルを待機するか、WaitForMultipleObjects を呼び出してイベント ハンドルの配列を待機し、1 つまたはすべてのイベント オブジェクトがシグナル通知されるかタイムアウト間隔が経過するまで中断できます。
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.
}