0Pricing
C# Academy · Lección

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

  1. IAsyncEnumerable y await foreach
  2. System.Threading.Channels
  3. ValueTask y cómo evitar asignaciones
  4. ConfigureAwait y contexto de sincronización
← Volver a C# Academy