System.Threading.Channels
Cree canalizaciones de productor-consumidor con Channel, ChannelWriter y ChannelReader para controlar la contrapresión.
System.Threading.Channels es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.
¿Qué son los canales?
System.Threading.Channels (introducido en .NET Core 3) proporciona una cola de productor-consumidor de alto rendimiento y segura para subprocesos. A diferencia de BlockingCollection, los canales son completamente asíncronos y eficientes en cuanto a asignaciones, por lo que resultan ideales para canalizaciones dentro del proceso.
Crear un canal
Los canales se crean mediante una factoría. Elija uno sin límite (sin capacidad máxima) o acotado (con capacidad limitada y contrapresión). La factoría devuelve un Channel<T> con un Writer y un Reader.
using System.Threading.Channels;
// Unbounded: unlimited capacity, no backpressure
var unbounded = Channel.CreateUnbounded<string>();
// Bounded: max 100 items; writer waits when full
var bounded = Channel.CreateBounded<string>(100);
// Bounded with drop-oldest strategy:
var dropping = Channel.CreateBounded<string>(new BoundedChannelOptions(50)
{
FullMode = BoundedChannelFullMode.DropOldest
});Escribir en un canal
Los productores escriben elementos mediante WriteAsync (espera si el canal acotado está lleno) o TryWrite (devuelve false inmediatamente si está lleno). Señale la finalización con Writer.Complete().
var channel = Channel.CreateUnbounded<int>();
// Producer task
var producer = Task.Run(async () =>
{
for (int i = 0; i < 100; i++)
{
await channel.Writer.WriteAsync(i);
await Task.Delay(10);
}
channel.Writer.Complete(); // signal no more items
});Leer de un canal
Los consumidores leen con ReadAllAsync() (el enfoque más sencillo) o con ReadAsync/TryRead para tener más control. ReadAllAsync finaliza cuando el escritor llama a Complete().
// Consumer task
var consumer = Task.Run(async () =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
{
Console.WriteLine($"Processed: {item}");
// Naturally paced — waits for next item
}
Console.WriteLine("Channel completed");
});
await Task.WhenAll(producer, consumer);Varios productores
Los canales son seguros para subprocesos. Varios productores pueden escribir simultáneamente, sin necesidad de bloqueos. Llame a Writer.Complete() solo después de que finalicen todos los productores.
var channel = Channel.CreateUnbounded<WorkItem>();
var producers = Enumerable.Range(0, 4).Select(id =>
Task.Run(async () =>
{
for (int i = 0; i < 25; i++)
await channel.Writer.WriteAsync(new WorkItem(id, i));
}));
// Wait for all producers before completing the writer
await Task.WhenAll(producers);
channel.Writer.Complete();Varios consumidores (fan-out)
Ejecute varias tareas consumidoras sobre el mismo lector del canal para procesar en paralelo. Cada elemento se entrega exactamente a un consumidor (se divide entre ellos, no se difunde a todos).
var channel = Channel.CreateBounded<WorkItem>(100);
// 4 parallel consumers
var consumers = Enumerable.Range(0, 4).Select(id =>
Task.Run(async () =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
{
await ProcessItemAsync(item);
Console.WriteLine($"Consumer {id} processed {item.Id}");
}
}));
await Task.WhenAll(consumers);Patrón de canalización
Encadene varios canales para formar una canalización de procesamiento: cada etapa lee de un canal, procesa los elementos y escribe en el siguiente. Las etapas se ejecutan simultáneamente con una contrapresión natural.
// Stage 1: raw data
var stage1 = Channel.CreateBounded<string>(50);
// Stage 2: parsed
var stage2 = Channel.CreateBounded<ParsedRecord>(50);
// Stage 3: enriched
var stage3 = Channel.CreateBounded<EnrichedRecord>(50);
var parse = ParseStageAsync(stage1.Reader, stage2.Writer);
var enrich = EnrichStageAsync(stage2.Reader, stage3.Writer);
var persist = PersistStageAsync(stage3.Reader);
await Task.WhenAll(parse, enrich, persist);Contrapresión con canales acotados
Un canal acotado aplica automáticamente contrapresión: cuando el canal está lleno, WriteAsync suspende al productor hasta que el consumidor extrae elementos. No se necesita código de limitación manual.
// Bounded channel: max 10 items
var channel = Channel.CreateBounded<string>(10);
// Fast producer
var producer = Task.Run(async () =>
{
for (int i = 0; i < 1000; i++)
{
// WriteAsync waits when channel has 10 items
await channel.Writer.WriteAsync($"item-{i}");
// Producer is naturally slowed to consumer speed
}
channel.Writer.Complete();
});
// Slow consumer
var consumer = Task.Run(async () =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
{
await Task.Delay(50); // simulate slow processing
Console.WriteLine(item);
}
});Gestión de errores en canales
Pase una excepción a Writer.Complete(exception) para propagar los errores a todos los lectores en espera. Los lectores reciben la excepción cuando se vuelve a leer del canal.
var channel = Channel.CreateUnbounded<int>();
var producer = Task.Run(async () =>
{
try
{
for (int i = 0; i < 100; i++)
{
if (i == 50) throw new Exception("Producer failed at 50");
await channel.Writer.WriteAsync(i);
}
channel.Writer.Complete();
}
catch (Exception ex)
{
channel.Writer.Complete(ex); // propagate to reader
}
});
try
{
await foreach (var item in channel.Reader.ReadAllAsync())
Console.WriteLine(item);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Channel error: {ex.Message}");
}Caso real: cola de trabajos en segundo plano
Una cola de trabajos en segundo plano que usa un servicio hospedado y un canal: las solicitudes HTTP ponen trabajos en cola y un trabajador en segundo plano los procesa de uno en uno.
public class JobQueue
{
private readonly Channel<Func<CancellationToken, Task>> _queue
= Channel.CreateBounded<Func<CancellationToken, Task>>(100);
public ChannelWriter<Func<CancellationToken, Task>> Writer => _queue.Writer;
public ChannelReader<Func<CancellationToken, Task>> Reader => _queue.Reader;
}
public class JobProcessor : BackgroundService
{
private readonly JobQueue _queue;
public JobProcessor(JobQueue q) => _queue = q;
protected override async Task ExecuteAsync(CancellationToken ct)
{
await foreach (var job in _queue.Reader.ReadAllAsync(ct))
await job(ct);
}
}Comprobación rápida
¿Qué ocurre cuando un productor llama a WriteAsync en un canal acotado lleno?
Resumen: System.Threading.Channels
Conclusiones clave:
- Los canales proporcionan una cola de productor-consumidor de alto rendimiento y nativamente asíncrona
- Sin límite: sin capacidad máxima; acotado: capacidad finita con contrapresión o estrategias de descarte
- Varios productores y consumidores pueden trabajar de forma segura sin bloqueos
- ReadAllAsync() + await foreach = el patrón de consumo más limpio
- Encadene canales en canalizaciones para procesar de forma simultánea por etapas
- Llame a Writer.Complete() (o Complete(exception)) para señalar el final del stream
Preguntas frecuentes
¿La lección «System.Threading.Channels» es gratis?
Sí — el texto completo de «System.Threading.Channels» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.
¿Qué aprenderé en «System.Threading.Channels»?
Cree canalizaciones de productor-consumidor con Channel, ChannelWriter y ChannelReader para controlar la contrapresión. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar C# Academy?
No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «System.Threading.Channels»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de C# Academy?
Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- IAsyncEnumerable y await foreach
- System.Threading.Channels
- ValueTask y cómo evitar asignaciones
- ConfigureAwait y contexto de sincronización