Gestion de l'accès concurrentiel avec DependentTransaction

L'objet Transaction est créé à l'aide de la méthode DependentClone. Il garantit que la transaction n'est pas validée tant que d'autres parties de code (par exemple, un thread de travail) travaillent encore sur la transaction. Une fois le travail sur la transaction clonée effectué et prêt pour validation, il peut informer le créateur de la transaction à l'aide de la méthode Complete. Cela vous permet de conserver la cohérence et l'exactitude des données.

La classe DependentTransaction permet également de gérer l'accès concurrentiel entre des tâches asynchrones. Dans ce cas, la transaction parent peut continuer d'exécuter du code tandis que le clone dépendant travaille sur ses propres tâches. En d'autres termes, l'exécution de la transaction parent n'est pas bloquée lorsque le clone dépendant travaille.

Création d'un clone dépendant

Pour créer une transaction dépendante, appelez la méthode DependentClone et passez l'énumération DependentCloneOption en tant que paramètre. Ce paramètre définit le comportement de la transaction lorsque Commit est appelé sur la transaction parent avant que le clone dépendant n'indique qu'il est prêt pour validation de la transaction (par appel à la méthode Complete). Les valeurs valides pour ce paramètre sont les suivantes :

  • BlockCommitUntilComplete crée une transaction dépendante qui bloque le processus de validation de la transaction parente jusqu’à ce que la transaction parente expire ou jusqu’à ce que Complete soit appelée sur tous les dépendants indiquant leur achèvement. Cela est utile lorsque le client ne souhaite pas valider la transaction parent avant la fin des transactions dépendantes. Si la transaction parent termine son travail avant les transactions dépendantes et qu'elle appelle Commit sur la transaction, le processus de validation est bloqué à un état permettant un travail supplémentaire sur la transaction et la création d'inscriptions jusqu'à ce que tous les clones dépendants aient appelé Complete. Une fois qu'ils ont tous terminé leur travail et appelé Complete, le processus de validation de la transaction démarre.

  • RollbackIfNotComplete crée une transaction dépendante qui est automatiquement abandonnée si Commit est appelée sur la transaction parent avant l'appel à Complete. Dans ce cas, l'ensemble du travail effectué dans la transaction dépendante reste intact au cours d'une transaction et personne ne peut en valider une seule partie.

La méthode Complete ne doit être appelée qu'une fois lorsque votre application termine son travail sur la transaction dépendante ; sinon, une InvalidOperationException est levée. Après cet appel, ne tentez pas d'effectuer un travail supplémentaire sur la transaction sous peine de lever une exception.

L'exemple de code suivant montre comment créer une transaction dépendante pour la gestion de deux tâches concurrentes, en clonant une transaction dépendante et en la passant à un thread de travail.

public class WorkerThread  
{  
    public void DoWork(DependentTransaction dependentTransaction)  
    {  
        Thread thread = new Thread(ThreadMethod);  
        thread.Start(dependentTransaction);
    }  
  
    public void ThreadMethod(object transaction)
    {
        DependentTransaction dependentTransaction = transaction as DependentTransaction;  
        Debug.Assert(dependentTransaction != null);
        try  
        {  
            using(TransactionScope ts = new TransactionScope(dependentTransaction))  
            {  
                /* Perform transactional work here */
                ts.Complete();  
            }  
        }  
        finally  
        {  
            dependentTransaction.Complete();
             dependentTransaction.Dispose();
        }  
    }  
  
//Client code
using(TransactionScope scope = new TransactionScope())  
{  
    Transaction currentTransaction = Transaction.Current;  
    DependentTransaction dependentTransaction;
    dependentTransaction = currentTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete);  
    WorkerThread workerThread = new WorkerThread();  
    workerThread.DoWork(dependentTransaction);  
    /* Do some transactional work here, then: */  
    scope.Complete();  
}  

Le code client crée une étendue transactionnelle qui définit également la transaction ambiante. Ne passez pas la transaction ambiante au thread de travail. À la place, clonez la transaction (ambiante) en cours en appelant la méthode DependentClone sur la transaction en cours et passez le clone dépendant au thread de travail.

La méthode ThreadMethod s'exécute sur le nouveau thread. Le client lance un nouveau thread, passant la transaction dépendante en tant que paramètre ThreadMethod.

La transaction dépendante est créée avec BlockCommitUntilComplete, la transaction ne peut donc pas être validée avant que le travail transactionnel ne soit effectué sur le deuxième thread et que Complete soit appelée sur la transaction dépendante. Cela signifie que si l’étendue du client se termine (lors de sa tentative de supprimer l’objet de transaction à la fin de l’instruction using) avant que le nouveau thread appelle Complete sur la transaction dépendante, le code client se bloque jusqu’à ce que Complete soit appelée sur la transaction dépendante. La transaction peut ensuite terminer la validation ou l'abandon.

Problèmes liés à l'accès concurrentiel

Vous devez avoir connaissance de quelques autres problèmes liés à l'accès concurrentiel lorsque vous utilisez la classe DependentTransaction :

  • Si le thread de travail restaure la transaction mais que le parent tente sa validation, une TransactionAbortedException est levée.

  • Vous devez créer un clone dépendant pour chaque thread de travail de la transaction. Veillez à ne pas passer un même clone dépendant à plusieurs threads, car seul l'un d'entre eux peut appeler Complete sur ce clone.

  • Si le thread de travail génère un autre thread de travail, créez un clone dépendant à partir du clone dépendant et passez-le au nouveau thread.

Voir aussi