C# Academy · Lektion

System.Threading.Channels

Bygg producent-konsument-pipelines med Channel, ChannelWriter och ChannelReader för att kontrollera backpressure.

Lektion 2 av 412 steg

System.Threading.Channels är en gratis lektion i C# Academy på CoddyKit. Detta är lektion 2 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för C# Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i C# Academy innehåller totalt 4 lektioner.

Vad är Channels?

System.Threading.Channels (introducerades i .NET Core 3) tillhandahåller en högpresterande, trådsäker producent-konsument-kö. Till skillnad från BlockingCollection är Channels helt asynkrona och allokeringseffektiva — idealiska för pipelines i samma process.

Skapa en Channel

Channels skapas med en fabrik. Välj unbounded (ingen gräns) eller bounded (begränsad kapacitet med backpressure). Fabriken returnerar en Channel<T> med en Writer och en 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
});

Skriva till en Channel

Producenter skriver objekt med WriteAsync (väntar om en begränsad kanal är full) eller TryWrite (returnerar false omedelbart om kanalen är full). Signalera slutförande med 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
});

Läsa från en Channel

Konsumenter läser med ReadAllAsync() (det enklaste sättet) eller ReadAsync/TryRead för större kontroll. ReadAllAsync slutförs när skrivaren anropar 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);

Flera producenter

Channels är trådsäkra. Flera producenter kan skriva samtidigt — ingen låsning krävs. Anropa Writer.Complete() först när alla producenter har slutfört sitt arbete.

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();

Flera konsumenter (fan-out)

Kör flera konsumentuppgifter mot samma kanalläsare för att parallellisera bearbetningen. Varje objekt levereras till exakt en konsument (partitionerat, inte som broadcast).

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);

Pipeline-mönstret

Sammanlänka kanaler till en bearbetningspipeline: varje steg läser från en kanal, bearbetar objekten och skriver till nästa. Stegen körs samtidigt med naturlig backpressure.

// 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);

Backpressure med begränsade kanaler

En begränsad kanal tillämpar automatiskt backpressure: när kanalen är full pausas WriteAsync tills konsumenten har tömt ut objekt. Ingen manuell strypningskod behövs.

// 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);
    }
});

Felhantering i Channels

Skicka ett undantag till Writer.Complete(exception) för att vidarebefordra fel till alla väntande läsare. Läsarna får undantaget vid nästa läsning från kanalen.

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}");
}

Verklighetsnära: kö för bakgrundsjobb

En kö för bakgrundsjobb som använder en värdtjänst och en kanal: HTTP-förfrågningar lägger jobb i kön och en bakgrundsarbetare bearbetar dem ett i taget.

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);
    }
}

Snabbtest

Vad händer när en producent anropar WriteAsync på en full begränsad kanal?

Repetition: System.Threading.Channels

Viktiga slutsatser:

  • Channels tillhandahåller en högpresterande, asynkron producent-konsument-kö
  • Unbounded: ingen gräns; Bounded: begränsad kapacitet med backpressure eller strategier för att kasta bort objekt
  • Flera producenter och konsumenter kan användas säkert utan låsning
  • ReadAllAsync() + await foreach = det enklaste sättet att läsa från en kanal
  • Sammanlänka kanaler till pipelines för samtidig bearbetning i steg
  • Anropa Writer.Complete() (eller Complete(exception)) för att signalera slutet på strömmen
Gratis att börja

Lär dig C# med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
93
Lektioner
346

Vanliga frågor

Är lektionen ”System.Threading.Channels” gratis?

Ja – hela texten till ”System.Threading.Channels” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i C# Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i C# Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”System.Threading.Channels”?

Bygg producent-konsument-pipelines med Channel, ChannelWriter och ChannelReader för att kontrollera backpressure. Ni övar på C# Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig C# Academy?

Du behöver inga förkunskaper. Utbildningen i C# Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.

Hur lång tid tar lektionen ”System.Threading.Channels”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här C# Academy-lektionen?

Ja. Varje C# Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. IAsyncEnumerable och await foreach
  2. System.Threading.Channels
  3. ValueTask och undvika allokeringar
  4. ConfigureAwait och synkroniseringskontext
← Tillbaka till C# Academy