Управление параллелизмом с помощью класса DependentTransaction

Объект Transaction создается с помощью метода DependentClone. Его единственная цель — гарантировать невозможность фиксации транзакции, пока некоторые фрагменты кода (например рабочий поток) еще выполняют операции над транзакцией. Когда операции в рамках клонированной транзакции завершены и готовы к фиксации, создателю транзакции может быть передано соответствующее уведомление с помощью метода Complete. Это позволяет сохранить согласованность и правильность данных.

Класс DependentTransaction также можно использовать для управления параллелизмом при выполнении асинхронных задач. В этом сценарии родительская транзакция может продолжать выполнение любого кода, пока ее зависимый клон выполняет свои задачи. Другими словами, выполнение родительской транзакции не блокируется до завершения выполнения зависимого клона.

Создание зависимого клона

Чтобы создать зависимую транзакцию, вызовите метод DependentClone, передав ему в качестве параметра перечисление DependentCloneOption. Этот параметр определяет поведение транзакции, когда метод Commit родительской транзакции вызывается до того, как зависимый клон указывает, что он готов к фиксации транзакции (путем вызова метода Complete). Этот параметр может принимать следующие значения.

  • BlockCommitUntilComplete создает зависимые транзакции, блокируя процесс фиксации родительской транзакции до истечения времени ожидания родительской транзакции или до Complete вызова всех зависимых элементов, указывающих на их завершение. Этот параметр полезно использовать, когда фиксация транзакции должна быть выполнена только после завершения зависимых транзакций. Если родительская транзакция завершает свои операции раньше зависимой транзакции и вызывает метод Commit, процесс фиксации блокируется для выполнения над транзакцией дополнительных операций и создания новых зачислений; блокировка снимается, когда все зависимые транзакции вызовут метод Complete. Когда все зависимые транзакции завершат свои операции и вызовут метод Complete, начинается процесс фиксации транзакции.

  • RollbackIfNotComplete. Этот параметр, напротив, позволяет создать зависимую транзакцию, которая автоматически прерывается, если метод Commit родительской транзакции вызывается до метода Complete. В этом случае все операции зависимой транзакции выполняются как один блок, который нельзя зафиксировать частично.

Метод Complete должен быть вызван только один раз после завершения обработки зависимой транзакции приложением; в противном случае возникает исключение InvalidOperationException. Попытка выполнить какие-либо дополнительные операции над транзакцией после вызова этого метода приведет к возникновению исключения.

В следующем примере кода показано, как создать зависимую транзакцию для управления двумя параллельными задачами путем клонирования текущей транзакции и передачи клона рабочему потоку.

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();  
}  

Клиентский код создает область транзакции, которая также задает внешнюю транзакцию. Не следует передавать внешнюю транзакцию рабочему потоку. Вместо этого следует клонировать текущую (внешнюю) транзакцию путем вызова метода DependentClone для текущей транзакции и передачи зависимой транзакции рабочему потоку.

Метод ThreadMethod выполняется в новом потоке. Клиентский код запускает новый поток, передавая зависимую транзакцию в качестве параметра ThreadMethod.

Поскольку зависимая транзакция создается с параметром BlockCommitUntilComplete, исходная транзакция может быть зафиксирована только после завершения всех операций во втором потоке и вызова метода Complete зависимой транзакции. Это означает, что если область клиента заканчивается (при попытке удалить объект транзакции в конце using инструкции) перед вызовом нового потока Complete для зависимой транзакции, клиентский код блокируется до Complete вызова зависимой. После этого может быть завершен процесс фиксации или прерывания транзакции.

Проблемы параллелизма

При использовании класса DependentTransaction следует помнить о нескольких дополнительных проблемах параллелизма.

  • Если рабочий поток откатывает транзакцию, а родительская транзакция пытается ее зафиксировать, возникает исключение TransactionAbortedException.

  • Для каждого рабочего потока в транзакции следует создавать новый зависимый клон. Не следует передавать один и тот же зависимый клон нескольким потокам, поскольку только один из них может вызвать метод Complete зависимого клона.

  • Если рабочий поток порождает новый рабочий поток, следует создать новый зависимый клон из существующего зависимого клона и передать его новому потоку.

См. также