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.