Exécution d'opérations asynchrones

SQL Server permet aux applications d'effectuer des opérations de base de données asynchrones. Le traitement asynchrone permet aux méthodes d'être retournées immédiatement sans blocage du thread appelant. Cela rend le multithreading plus puissant et plus souple, sans obliger le développeur à créer explicitement des threads ou à gérer la synchronisation. Les applications requièrent un traitement asynchrone lors de l'initialisation d'une connexion de base de données, ou lors de l'initialisation du résultat de l'exécution d'une commande.

Ouverture et fermeture d'une connexion de base de données

Lors de l'utilisation du fournisseur OLE DB de SQL Server Native Client, les applications conçues pour initialiser un objet source de données de façon asynchrone peuvent définir DBPROPVAL_ASYNCH_INITIALIZE dans la propriété DBPROP_INIT_ASYNCH avant d'appeler IDBInitialize::Initialize. Lorsque cette propriété est définie, le fournisseur est retourné immédiatement à partir de l'appel à Initialize avec la valeur S_OK, si l'opération s'est effectuée immédiatement, ou DB_S_ASYNCHRONOUS, si l'initialisation se poursuit de façon asynchrone. Les applications peuvent interroger l'interface IDBAsynchStatus ou ISSAsynchStatus sur l'objet source de données, puis appeler IDBAsynchStatus::GetStatus ou ISSAsynchStatus::WaitForAsynchCompletion pour obtenir l'état de l'initialisation.

De plus, la propriété SSPROP_ISSAsynchStatus a été ajoutée au jeu de propriétés DBPROPSET_SQLSERVERROWSET. Les fournisseurs qui prennent en charge l'interface ISSAsynchStatus doivent implémenter cette propriété avec la valeur VARIANT_TRUE.

IDBAsynchStatus::Abort ou ISSAsynchStatus::Abort peut être appelé pour annuler l'appel asynchrone à Initialize. Le consommateur doit demander explicitement l'initialisation de la source de données asynchrone. Sinon, IDBInitialize::Initialize n'est pas retourné tant que l'objet source de données n'est pas complètement initialisé.

[!REMARQUE]

Les objets sources de données utilisés pour le regroupement de connexions ne peuvent pas appeler l'interface ISSAsynchStatus dans le fournisseur OLE DB de SQL Server Native Client. L'interface ISSAsynchStatus n'est pas exposée pour les objets sources de données regroupés.

Si une application force explicitement l'utilisation du moteur de curseur, IOpenRowset::OpenRowset et IMultipleResults::GetResult ne prennent pas en charge le traitement asynchrone.

De plus, la dll proxy/stub distante (dans MDAC 2.8) ne peut pas appeler l'interface ISSAsynchStatus dans SQL Server Native Client. L'interface ISSAsynchStatus n'est pas exposée via l'accès distant.

Les composants de service ne prennent pas en charge ISSAsynchStatus.

Exécution et initialisation de l'ensemble de lignes

Les applications conçues pour ouvrir de façon asynchrone le résultat de l'exécution d'une commande peuvent définir DBPROPVAL_ASYNCH_INITIALIZE dans la propriété DBPROP_ROWSET_ASYNCH. Lors de la définition de DBPROPVAL_ASYNCH_INITIALIZE, avant d'appeler IDBInitialize::Initialize, ICommand::Execute, IOpenRowset::OpenRowset ou IMultipleResults::GetResult, l'argument riid doit être défini à IID_IDBAsynchStatus, IID_ISSAsynchStatus ou IID_IUnknown.

La méthode est retournée immédiatement avec S_OK si l'initialisation de l'ensemble de lignes s'effectue immédiatement (ou avec DB_S_ASYNCHRONOUS si l'ensemble de lignes poursuit son initialisation de façon asynchrone) avec ppRowset défini en fonction de l'interface demandée sur l'ensemble de lignes. Pour le fournisseur OLE DB de SQL Server Native Client, cette interface peut être uniquement IDBAsynchStatus ou ISSAsynchStatus. Jusqu'à ce que l'ensemble de lignes soit entièrement initialisé, cette interface se comporte comme si elle était dans un état suspendu ; par ailleurs, l'appel de QueryInterface pour les interfaces différentes de IID_IDBAsynchStatus ou IID_ISSAsynchStatus peut retourner E_NOINTERFACE. À moins que le consommateur ne demande explicitement un traitement asynchrone, l'ensemble de lignes est initialisé de façon synchrone. Toutes les interfaces demandées sont disponibles lorsque IDBAsynchStaus::GetStatus ou ISSAsynchStatus::WaitForAsynchCompletion est retourné avec une indication montrant que l'opération asynchrone a été effectuée. Cela ne signifie pas nécessairement que l'ensemble de lignes soit entièrement rempli, mais il est prêt et complètement fonctionnel.

Si la commande exécutée ne retourne pas d'ensemble de lignes, elle est quand même retournée immédiatement avec un objet qui prend en charge IDBAsynchStatus.

S'il vous faut obtenir plusieurs résultats à partir de l'exécution d'une commande asynchrone, vous devez :

  • définir DBPROPVAL_ASYNCH_INITIALIZE dans la propriété DBPROP_ROWSET_ASYNCH, avant d'exécuter la commande ;

  • appeler ICommand::Execute et demander IMultipleResults.

Les interfaces IDBAsynchStatus et ISSAsynchStatus peuvent ensuite être obtenues en interrogeant l'interface de résultats multiples via QueryInterface.

Lorsque l'exécution de la commande est terminée, IMultipleResults peut être utilisé normalement à une exception près par rapport au traitement synchrone : DB_S_ASYNCHRONOUS peut être retourné, auquel cas IDBAsynchStatus ou ISSAsynchStatus peut être utilisé pour déterminer le moment où l'opération est achevée.

Exemples

Dans l'exemple suivant, l'application appelle une méthode non bloquante, effectue d'autres traitements, puis retourne au traitement des résultats. ISSAsynchStatus::WaitForAsynchCompletion attend l'objet d'événement interne jusqu'à ce que l'opération d'exécution asynchrone soit terminée ou que le délai spécifié par dwMilisecTimeOut soit passé.

// Set the DBPROPVAL_ASYNCH_INITIALIZE bit in the 
// DBPROP_ROWSET_ASYNCH property before calling Execute().

DBPROPSET CmdPropset[1];
DBPROP CmdProperties[1];

CmdPropset[0].rgProperties = CmdProperties;
CmdPropset[0].cProperties = 1;
CmdPropset[0].guidPropertySet = DBPROPSET_ROWSET;

// Set asynch mode for command.
CmdProperties[0].dwPropertyID = DBPROP_ROWSET_ASYNCH;
CmdProperties[0].vValue.vt = VT_I4;
CmdProperties[0].vValue.lVal = DBPROPVAL_ASYNCH_INITIALIZE;
CmdProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;

hr = pICommandProps->SetProperties(1, CmdPropset);

hr = pICommand->Execute(
   pUnkOuter,
   IID_ISSAsynchStatus,
   pParams,
   pcRowsAffected,
   (IUnknown**)&pISSAsynchStatus);

if (hr == DB_S_ASYNCHRONOUS)
{
   // Do some work here...

   hr = pISSAsynchStatus->WaitForAsynchCompletion(dwMilisecTimeOut);
   if ( hr == S_OK)
   {
      hr = pISSAsynchStatus->QueryInterface(IID_IRowset, (void**)&pRowset);
      pISSAsynchStatus->Release();
   }
}

ISSAsynchStatus::WaitForAsynchCompletion attend l'objet d'événement interne jusqu'à ce que l'opération d'exécution asynchrone soit terminée ou que la valeur dwMilisecTimeOut soit passée.

L'exemple suivant illustre le traitement asynchrone avec plusieurs jeux de résultats :

DBPROP CmdProperties[1];

// Set asynch mode for command.
CmdProperties[0].dwPropertyID = DBPROP_ROWSET_ASYNCH;
CmdProperties[0].vValue.vt = VT_I4;
CmdProperties[0].vValue.lVal = DBPROPVAL_ASYNCH_INITIALIZE;

hr = pICommand->Execute(
   pUnkOuter,
   IID_IMultipleResults,
   pParams,
   pcRowsAffected,
   (IUnknown**)&pIMultipleResults);

// Use GetResults for ISSAsynchStatus.
hr = pIMultipleResults->GetResult(IID_ISSAsynchStatus, (void **) &pISSAsynchStatus);

if (hr == DB_S_ASYNCHRONOUS)
{
   // Do some work here...

   hr = pISSAsynchStatus->WaitForAsynchCompletion(dwMilisecTimeOut);
   if (hr == S_OK)
   {
      hr = pISSAsynchStatus->QueryInterface(IID_IRowset, (void**)&pRowset);
      pISSAsynchStatus->Release();
   }
}

Pour empêcher tout blocage, le client peut vérifier l'état d'une opération asynchrone en cours d'exécution, comme le montre l'exemple suivant :

// Set the DBPROPVAL_ASYNCH_INITIALIZE bit in the 
// DBPROP_ROWSET_ASYNCH property before calling Execute().
hr = pICommand->Execute(
   pUnkOuter,
   IID_ISSAsynchStatus,
   pParams,
   pcRowsAffected,
   (IUnknown**)&pISSAsynchStatus); 

if (hr == DB_S_ASYNCHRONOUS)
{
   do{
      // Do some work...
      hr = pISSAsynchStatus->GetStatus(DB_NULL_HCHAPTER, DBASYNCHOP_OPEN, NULL, NULL, &ulAsynchPhase, NULL);
   }while (DBASYNCHPHASE_COMPLETE != ulAsynchPhase)
   if SUCCEEDED(hr)
   {
      hr = pISSAsynchStatus->QueryInterface(IID_IRowset, (void**)&pRowset);
   }
   pIDBAsynchStatus->Release();
}

L'exemple suivant montre comment vous pouvez annuler l'opération asynchrone en cours d'exécution :

// Set the DBPROPVAL_ASYNCH_INITIALIZE bit in the 
// DBPROP_ROWSET_ASYNCH property before calling Execute().
hr = pICommand->Execute(
   pUnkOuter,
   IID_ISSAsynchStatus,
   pParams,
   pcRowsAffected,
   (IUnknown**)&pISSAsynchStatus);

if (hr == DB_S_ASYNCHRONOUS)
{
   // Do some work...
   hr = pISSAsynchStatus->Abort(DB_NULL_HCHAPTER, DBASYNCHOP_OPEN);
}