ExecutionContext

Algum dia desses eu utilizei uma classe bastante interessante fornecida pelo namespace System.Threading, chamada ExecutionContext. Ela tem a capacidade de gerenciar o contexto de execução (segurança, sincronização e transações) da thread atual, fornecendo métodos que podemos utilizar para evitar ou possibilitar a propagação do contexto para os métodos assíncronos que a nossa aplicação possa utilizar.

Quando invocamos um método assíncrono, via delegate ou via ThreadPool, o contexto é sempre propagado entre as várias threads que a aplicação está utilizando e, conseqüentemente, voce terá acesso as mesmas informações com relação a execução da thread principal e, é neste cenário, que a classe em questão entra em ação. Ela fornece quatro métodos importantes, a saber:

  • Capture: Método estático que captura o contexto de execução atual.
  • SupressFlow: Suprime a propagação do contexto entre as threads assíncronas.
  • Run: Executa um método (através de um delegate ContextCallback) em um contexto específico.
  • RestoreFlow: Restaura a propagação do contexto entre as threads assíncronas.

Para exemplificar esses quatro métodos, o código abaixo utiliza uma função chamada LogonUser que permite efetuar o logon de um determinado usuário. Se a autenticação for realizada com sucesso, personificamos a thread atual para este usuário.

using System;
using System.Threading;
using System.Globalization;
using System.Security.Principal;
using System.Runtime.InteropServices;

namespace AsyncContext
{
    class Program
    {
        [DllImport(“advapi32.dll”)]
        private static extern bool LogonUser(
            String lpszUsername,
            String lpszDomain,
            String lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            out IntPtr phToken);

        static void Main(string[] args)
        {
            IntPtr token;
            if (LogonUser(“Teste”, string.Empty, “123456”, 2, 0, out token))
            {
                WindowsIdentity.Impersonate(token);
                ThreadPool.QueueUserWorkItem(Callback, 1);
                ExecutionContext ec = ExecutionContext.Capture();
                ExecutionContext.SuppressFlow();
                    ThreadPool.QueueUserWorkItem(Callback, 2);
                    ExecutionContext.Run(ec, new ContextCallback(Callback), 3);
                    ThreadPool.QueueUserWorkItem(Callback, 4);
                ExecutionContext.RestoreFlow();
                ThreadPool.QueueUserWorkItem(Callback, 5);
                token = IntPtr.Zero;
            }
            Console.ReadLine();
        }

        static void Callback(object state)
        {
            Console.WriteLine(“Id: {0}tThread: {1}tUser: {2}”,
                state,
                Thread.CurrentThread.ManagedThreadId,
                WindowsIdentity.GetCurrent().Name);
        }
    }
}

O método chamado Callback é utilizado pelos delegates para a execução do trabalho assíncrono. Ele exibe em seu interior informações como o estado (object) que é passado como parametro para o método, id da thread corrente, o nome do usuário corrente.

O resultado da execução deste código é:

Id: 1   Thread: 3   User: ISRAELAECETeste
Id: 2   Thread: 3   User: ISRAELAECEIsrael Aece
Id: 3   Thread: 1   User: ISRAELAECETeste
Id: 4   Thread: 3   User: ISRAELAECEIsrael Aece
Id: 5   Thread: 3   User: ISRAELAECETeste

Analisando o código do método Main, se o logon for realizado com sucesso, personificamos a thread atual para o usuário chamado Teste. Em seguida delegamos para a classe ThreadPool a execução do método Callback de forma assíncrona e resultará na exibição do usuário Teste (Id: 1). A partir daqui entra em cena a classe que é tema deste post.

Através do método estático Capture da classe ExecutionContext, ela retorna um objeto do mesmo tipo, representando o contexto atual e armazena em um objeto chamado ec. Agora, através do método SupressFlow, ele evita de propagar o contexto para os métodos assíncronos e, consequentemente, as requisições realizadas através do ThreadPool, resultarão em um outro nome de usuário (Id: 2). Como vimos acima, o método Run, dado um contexto e um delegate, ele é responsável por executar o método no contexto informado e, como podemos notar no código, passamos para o método ele a instancia do contexto que capturamos mais acima e que está armazenado no objeto ec e, além dele, passamos também uma instancia do delegate ContextCallback, apontando qual será o método que deve ser executado dentro daquele contexto específico. O resultado, como poderíamos esperar, é o usuário Teste (Id: 3). Finalmente, restauramos a propagação do contexto quando invocamos o método RestoreFlow e, se analisarmos a última execução a partir da ThreadPool, temos como resultadao o usuário Teste (Id: 5) novamente.

E, como a documentação diz, enquanto o contexto está suprimido, se invocar o método Capture, ele retornará nulo. Se ainda desejar verificar se o contexto está ou não suprimido, pode analisar isso através de um método chamado IsFlowSuppressed que retorna um valor booleano indicando essa informação.

Anúncios