¿Qué es la seguridad en hilos?
La seguridad en hilos (thread safety) se refiere a la capacidad de una clase, método u operación para comportarse correctamente cuando varios hilos acceden simultáneamente al mismo recurso compartido, sin necesidad de mecanismos de sincronización externos.
Por ejemplo, considera este código en C#:
int contador = 0;
void Incrementar()
{
contador++;
}
La operación contador++ no es atómica; consta de tres pasos: leer, incrementar, escribir. Si varios hilos ejecutan Incrementar al mismo tiempo, pueden perderse algunas sumas, dando un resultado menor al esperado.
Estrategias para lograr seguridad en hilos
1. Uso de lock (mutex)
private readonly object _candado = new object();
private int contador = 0;
public void Incrementar()
{
lock (_candado)
{
contador++;
}
}
El lock garantiza que solo un hilo ejecute ese bloque a la vez. Fácil de usar, pero tiene costo de rendimiento en alta concurrencia.
2. Operaciones atómicas con Interlocked
private int contador = 0;
public void Incrementar()
{
Interlocked.Increment(ref contador);
}
Ideal para operaciones simples sobre enteros, sin bloqueos.
3. Colecciones seguras para hilos
ConcurrentDictionary<TKey, TValue>ConcurrentQueue<T>ConcurrentBag<T>BlockingCollection<T>
Ejemplo:
var diccionario = new ConcurrentDictionary<string, int>();
diccionario.TryAdd("clave", 42);
4. Modificador volatile
private volatile bool _activo;
No resuelve atomicidad, pero garantiza que el valor sea leído desde la memoria principal (no desde caché del CPU), evitando problemas de visibilidad entre hilos.
5. ThreadLocal<T> para datos por hilo
ThreadLocal<int> contadorLocal = new ThreadLocal<int>(() => 0);
void Incrementar()
{
contadorLocal.Value++;
}
Cada hilo tiene su propia copia, eliminando la contención.
6. Evitar compartir recursos
Diseñar sistemas basados en paso de mensajes (ej. actores con Akka.NET) o colas de mensajes evita la necesidad de sincronización.
Restricción de acceso a la UI desde hilos secundarios
En frameworks .NET como WinForms, WPF, MAUI y Xamarin.Forms, los controles de UI solo pueden ser modificados desde el hilo que los creó (normalmente el hilo principal). Esto no es exactamente un problema de seguridad en hilos tradicional, sino una resrticción del modelo de un solo hilo de UI.
Ejemplo incorrecto (WinForms):
new Thread(() =>
{
etiqueta.Text = "Actualizando..."; // Lanza InvalidOperationException
}).Start();
Ejemplo correcto (WinForms):
new Thread(() =>
{
etiqueta.Invoke(() => { etiqueta.Text = "Actualizando..."; });
}).Start();
Ejemplo correcto (WPF):
Application.Current.Dispatcher.Invoke(() =>
{
textoBloque.Text = "Actualizado desde otro hilo";
});
Comparativa de modelos de hilo en .NET UI
| Framework | Modelo | Método para volver al hilo de UI |
|---|---|---|
| Windows Forms | Un solo hilo | Control.Invoke()/BeginInvoke() |
| WPF | Un solo hilo | Dispatcher.Invoke()/BeginInvoke() |
| .NET MAUI | Un solo hilo | MainThread.BeginInvokeOnMainThreadAsync() |
| Xamarin.Forms | Un solo hilo | Device.BeginInvokeOnMainThread() |
| Avalonia UI | Mayor flexibilidad | Dispatcher.UIThread.Post() |
Resumen de técnicas
| Técnica | Cuándo usarla |
|---|---|
lock |
Secciones críticas pequeñas |
Interlocked |
Operaciones atómicas sobre enteros |
| Colecciones concurrentes | Acceso concurrente a colecciones |
ThreadLocal<T> |
Datos independientes por hilo |
| Diseño sin estado compartido | Sistemas de alta concurrencia (actores/mensajes) |