Pokud potřebujeme nějaké operaci poskytnout data, občas se hodí, aby tyto data byla dostupná kdekoliv v kódu implementující danou operaci bez nutnosti předávat je parametrem apod. Platnost těchto dat je přitom ale logicky omezena jen na dobu vykonávání dané operace, takže by nebylo vhodné nebo při paralelním zpracování dokonce ani možné použití obyčejné statické proměnné. Za předpokladu, že je celé zpracování operace vykonáváno jen na jednom threadu, je docela elegantním řešením zavést kontext svázaný právě přímo s threadem.
Jak na to?
Řešení se skládá se dvou klíčových prvků. Prvním je použití ThreadStatic proměnné, ve které bude aktuální kontext držen a bude veřejně dostupný přes statickou vlastnost Current. Druhým je pak poskytnutí API umožňující jednoduché vymezení platnosti kontextu tj. inicializace a rušení kontextu. Aby pro tento účel šlo s výhodou použít blok using, je možné implementovat celý kontext jako disposable objekt.
Následující kód to ukazuje na třídě OperationContext, instanční data jsou zastoupena vlastností State:
public sealed class OperationContext : IDisposable
{
#region member varible and default property initialization
public object State { get; private set; }
[ThreadStatic]
private static OperationContext s_Current;
#endregion
#region constructors and destructors
public OperationContext(object state)
{
if (state == null)
{
throw new ArgumentNullException("state");
}
if (s_Current != null)
{
throw new InvalidOperationException("Current thread's context is already set.");
}
this.State = state;
s_Current = this;
}
#endregion
#region action methods
public void Dispose()
{
s_Current = null;
}
#endregion
#region property getters/setters
public static OperationContext Current
{
get { return s_Current; }
}
#endregion
}
Použití takto připravené třídy pak bude následující:
static void Main()
{
//Infrastruktura - inicializace kontextu pro operaci
using (new OperationContext("MyState"))
{
Operation();
}
}
static void Operation()
{
//Implementace vlastní operace - použití kontextu
Console.WriteLine(OperationContext.Current.State);
}
Je vidět, že vlastní implementace tohoto “patternu” i jeho použití je poměrně jednoduché.
Všimněte si ještě, že naše uvedená implementace neumožňuje kontexty “vnořovat” (viz kontrola v konstruktoru třídy OperationContext). Pokud by to náš scénář vyžadovat, stačilo by implementovat ThreadStatic storage jako zásobník a aktuální kontext by pak byl vždy prvek na vrcholu tohoto zásobníku.