Propagando Transações em Métodos Assíncronos


Desde a versão 2.0 do .NET Framework existe um assembly chamado System.Transactions.dll. Dentro deste assembly há diversos tipos para trabalharmos com transações dentro de aplicações .NET. Basicamente passamos a ter o controle, através de uma linguagem .NET, de um ambiente transacionado, em que podemos delimitar o escopo e decidir quando e onde queremos efetivar (commit) ou desfazer (rollback) as alterações .

Através deste mesmo conjunto de classes, temos a possibilidade de alistar vários tipos de recursos, e entre eles temos base de dados relacionais, filas de mensagens (message queue) e até mesmo, com algum trabalho, recursos voláteis. Além disso, este mecanismo é inteligente o bastante para determinar quando ele precisa apenas de uma transação local (quando envolve apenas um resource manager), e quando há mais que um envolvido, ele é capaz de escalar para uma transação distribuída de forma automática.

Apesar de funcionar bem, alguimas complicações começam a aparecer quando estamos trabalhando com aplicações assíncronas, mas que compartilham do mesmo escopo transacionado. O que quero dizer aqui é que uma vez que o escopo está criado (TransactionScope), se envolvermos chamadas à outros códigos de forma assíncrona, esperando que a transação seja propagada para essas outras threads, teremos um comportamento não desejado. Isso se deve ao fato de que, por padrão, a transação não é (automaticamente) propagada para essas outras threads que estão fazendo um trabalho complementar ao principal.

Para resolvermos este problema até então, devemos recorrer à classe DependentTransaction. Como o próprio nome sugere, esta classe é um clone da transação que rege o ambiente criado pelo TransactionScope, e garante que escopo principal não possa ser concluído antes que todos os trabalhos que estão sendo executados paralelamente estejam finalizados. A criação desta classe se dá através do método DependentClone da classe Transaction, que como parâmetro recebe uma das opções expostas pelo enumerador DependentCloneOption. No exemplo abaixo utilizaremos a opção BlockCommitUntilComplete, que garantirá que que transação não seja efetivada até que o trabalho dentro do método Metodo1 seja finalizado. O que sinaliza ao coordernador que o trabalho assíncrono foi concluído é a chamada para o método Complete da classe DependentTransaction.

private static void Executar()
{
    using (var scope = new TransactionScope())
    {
        LogTransactionInfo(“Main”);

        ThreadPool.QueueUserWorkItem(
            Metodo1,
            Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete));

        scope.Complete();
    }
}

private static void Metodo1(object root)
{
    using (var dependentTransaction = root as DependentTransaction)
    {
        using (var scope = new TransactionScope(dependentTransaction))
        {
            LogTransactionInfo(“Metodo1”);

            scope.Complete();
        }

        dependentTransaction.Complete();
    }
}

Se avaliarmos o log do identificador da transação, veremos que ambos possuem o mesmo ID:

Main: 5bb899d4-3428-42ce-9c45-d498900be040:1
Metodo1: 5bb899d4-3428-42ce-9c45-d498900be040:1

Como o .NET Framework em conjunto com as linguagens estão tentando tornar a construção de aplicações assíncronas mais simples, a Microsoft incluiu na versão 4.5.1 do .NET Framework um novo construtor na classe TransactionScope que aceita uma das duas opções expostas pelo enumerador TransactionScopeAsyncFlowOption. A opção Enabled que é utilizada abaixo indica ao .NET que o escopo transacionado deve ser propagado para os métodos assíncronos que são invocados dentro dele. Isso facilita a codificação, pois podemos tornar o código mais legível, sem a necessidade de ficar controlando detalhes de infraestrutura, e isso pode ser comprovado através do exemplo abaixo. O enumerador TransactionScopeAsyncFlowOption também possui a opção Supress, que é a configuração padrão e é indica que o contexto transacionado não seja propagado.

private async static Task Executar()
{
    using(var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
    {
        LogTransactionInfo(“Main”);

        await Metodo1();

        scope.Complete();
    }
}

private async static Task Metodo1()
{
    LogTransactionInfo(“Metodo1”);

    await Task.Delay(100);
}

E, finalmente, como já era de se esperar, os IDs das transações são idênticos, tanto na thread principal quanto na worker thread:

Main: d7f86e15-8bf8-4472-aeb7-be661a2c5703:1
Metodo1: d7f86e15-8bf8-4472-aeb7-be661a2c5703:1

Publicidade

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s