Gerenciar simultaneidade com DependentTransaction

O Transaction objeto é criado usando o DependentClone método. Sua única finalidade é garantir que a transação não confirmada enquanto alguns outros trechos de código (por exemplo, um thread de trabalho) ainda estão executando o trabalho na transação. Quando o trabalho realizado dentro da transação clonada é concluído e pronto para ser confirmada, ele pode notificar o criador da transação usando o Complete método. Portanto, você pode preservar a consistência e correção dos dados.

O DependentTransaction classe também pode ser usada para gerenciar a simultaneidade entre tarefas assíncronas. Nesse cenário, o pai pode continuar a executar qualquer código enquanto o clone dependente funciona em suas tarefas. Em outras palavras, a execução do pai não é bloqueada até que o dependente é concluída.

Criando um Clone dependente

Para criar uma transação dependente, chame o DependentClone método e passar o DependentCloneOption enumeração como um parâmetro. Esse parâmetro define o comportamento da transação se Commit é chamado na transação pai antes que o clone dependente indica que ele está pronto para a transação seja confirmada (chamando o Complete método). Os seguintes valores são válidos para esse parâmetro:

  • BlockCommitUntilComplete cria uma transação dependente que bloqueia o processo de confirmação da transação pai até a transação pai atingir o tempo limite ou até que Complete seja chamado em todos os dependentes indicando a conclusão. Isso é útil quando o cliente não deseja que a transação do pai seja confirmada até que as transações dependentes sejam concluídas. Se o pai concluir seu trabalho antes que a transação dependente e chamadas Commit na transação, o processo de confirmação será bloqueado em um estado onde trabalho adicional pode ser feito na transação e novas inscrições podem ser criadas, até que todos os da chamada dependentes Complete. Assim que todos eles concluiu seu trabalho e chame Complete, inicia o processo de confirmação da transação.

  • RollbackIfNotComplete, por outro lado, cria uma transação dependente que anula automaticamente se Commit for chamado na transação pai antes Complete de ser chamado. Nesse caso, todo o trabalho feito na transação dependente está intacto no tempo de vida de uma transação e não tem a oportunidade de confirmar apenas uma parte dele.

O Complete método deve ser chamado apenas uma vez quando seu aplicativo termina seu trabalho na transação dependente; Caso contrário, um InvalidOperationException é lançada. Depois que essa chamada é invocada, você não deve tentar qualquer trabalho adicional na transação ou uma exceção é lançada.

O exemplo de código a seguir mostra como criar uma transação dependente para gerenciar duas tarefas simultâneas por uma transação dependente de clonagem e passá-la para um thread de trabalho.

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

O código do cliente cria um escopo transacional que também define a transação de ambiente. Você não deve passar a transação de ambiente para o thread de trabalho. Em vez disso, você deve clonar a transação (ambiente) atual chamando o DependentClone método na transação atual e passe o dependente para o thread de trabalho.

O ThreadMethod método é executado no novo segmento. O cliente inicia um novo thread, passando a transação dependente como o ThreadMethod parâmetro.

Como a transação dependente é criada com BlockCommitUntilComplete, terá a garantia de que a transação não pode ser confirmada até que todos os do trabalho transacional feito no segundo thread é concluído e Complete é chamado na transação dependente. Isso significa que, se o escopo do cliente terminar (quando ele tentar descartar o objeto de transação no final da instrução using) antes do novo encadeamento chamar Complete na transação dependente, o código do cliente será bloqueado até que Complete seja chamado no dependente. Em seguida, a transação pode concluir a confirmação ou anulação.

Problemas de simultaneidade

Existem alguns problemas de simultaneidade adicionais que precisam ser consideradas ao usar o DependentTransaction classe:

  • Se o thread de trabalho reverte a transação, mas o pai tenta confirmar, um TransactionAbortedException é lançada.

  • Você deve criar um novo clone dependente para cada thread de trabalho na transação. Não passam o mesmo clone dependente em vários threads, porque apenas um deles pode chamar Complete nele.

  • Se o thread de trabalho gera um novo thread de trabalho, certifique-se de criar um clone dependente do clone dependente e passá-lo para o novo thread.

Confira também