System.Threading.Channels
สร้างสายงานแบบผู้ผลิต-ผู้บริโภคด้วย Channel, ChannelWriter และ ChannelReader เพื่อควบคุมแรงดันย้อนกลับ
System.Threading.Channels เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
แชนเนลคืออะไร
System.Threading.Channels (เริ่มใช้ใน .NET Core 3) มีคิวผู้ผลิต-ผู้บริโภคที่มีประสิทธิภาพสูงและปลอดภัยต่อเธรด ต่างจาก BlockingCollection แชนเนลทำงานแบบอะซิงโครนัสเต็มรูปแบบและจัดสรรหน่วยความจำอย่างมีประสิทธิภาพ จึงเหมาะอย่างยิ่งสำหรับไปป์ไลน์ภายในกระบวนการ
การสร้างแชนเนล
แชนเนลสร้างขึ้นด้วยตัวสร้าง เลือกแบบไม่จำกัดขนาด (ไม่มีขีดจำกัด) หรือแบบจำกัดขนาด (มีความจุจำกัดพร้อมแรงกดดันย้อนกลับ) ตัวสร้างจะส่งคืน Channel<T> ที่มี Writer และ 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
});การเขียนลงแชนเนล
ผู้ผลิตเขียนรายการโดยใช้ WriteAsync (รอหากแชนเนลแบบจำกัดขนาดเต็ม) หรือ TryWrite (ส่งคืนค่า false ทันทีหากเต็ม) ส่งสัญญาณว่าดำเนินการเสร็จสิ้นด้วย 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
});การอ่านจากแชนเนล
ผู้บริโภคอ่านข้อมูลด้วย ReadAllAsync() (แนวทางที่ง่ายที่สุด) หรือใช้ ReadAsync/TryRead เพื่อควบคุมได้มากขึ้น ReadAllAsync จะเสร็จสิ้นเมื่อผู้เขียนเรียก 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);ผู้ผลิตหลายราย
แชนเนลปลอดภัยต่อเธรด ผู้ผลิตหลายรายสามารถเขียนพร้อมกันได้โดยไม่ต้องล็อก เรียก Writer.Complete() หลังจากผู้ผลิตทั้งหมดทำงานเสร็จแล้วเท่านั้น
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();ผู้บริโภคหลายราย (การกระจายงาน)
เรียกใช้งานหลาย task ของผู้บริโภคกับตัวอ่านแชนเนลเดียวกัน เพื่อประมวลผลแบบขนาน แต่ละรายการจะถูกส่งให้ผู้บริโภคเพียงรายเดียวเท่านั้น (เป็นการแบ่งส่วน ไม่ใช่การกระจายไปให้ทุกคน)
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);รูปแบบไปป์ไลน์
เชื่อมแชนเนลเข้าด้วยกันเป็นไปป์ไลน์การประมวลผล: แต่ละขั้นตอนอ่านข้อมูลจากแชนเนลหนึ่ง ประมวลผลรายการ แล้วเขียนลงแชนเนลถัดไป ขั้นตอนต่าง ๆ จะทำงานพร้อมกันโดยมีแรงกดดันย้อนกลับตามธรรมชาติ
// 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);แรงกดดันย้อนกลับด้วยแชนเนลแบบจำกัดขนาด
แชนเนลแบบจำกัดขนาดจะใช้แรงกดดันย้อนกลับโดยอัตโนมัติ เมื่อแชนเนลเต็ม WriteAsync จะระงับผู้ผลิตไว้จนกว่าผู้บริโภคจะนำรายการออก ไม่จำเป็นต้องเขียนโค้ดควบคุมอัตราด้วยตนเอง
// 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);
}
});การจัดการข้อผิดพลาดในแชนเนล
ส่งข้อยกเว้นไปยัง Writer.Complete(exception) เพื่อส่งต่อข้อผิดพลาดไปยังผู้อ่านทั้งหมดที่กำลังรอ ผู้อ่านจะเห็นข้อยกเว้นเมื่ออ่านแชนเนลครั้งถัดไป
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}");
}การใช้งานจริง: คิวงานเบื้องหลัง
คิวงานเบื้องหลังที่ใช้บริการโฮสต์และแชนเนล: คำขอ HTTP จะนำงานเข้าคิว แล้วผู้ปฏิบัติงานเบื้องหลังจะประมวลผลทีละงาน
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);
}
}ตรวจสอบความเข้าใจ
จะเกิดอะไรขึ้นเมื่อผู้ผลิตเรียก WriteAsync บนแชนเนลแบบจำกัดขนาดที่เต็มแล้ว
สรุป: System.Threading.Channels
ประเด็นสำคัญ:
- แชนเนลมีคิวผู้ผลิต-ผู้บริโภคที่มีประสิทธิภาพสูงและรองรับอะซิงโครนัสโดยธรรมชาติ
- แบบไม่จำกัดขนาด: ไม่มีขีดจำกัด แบบจำกัดขนาด: มีความจุจำกัดพร้อมแรงกดดันย้อนกลับหรือกลยุทธ์การทิ้งข้อมูล
- ผู้ผลิตและผู้บริโภคหลายรายทำงานได้อย่างปลอดภัยโดยไม่ต้องล็อก
- ReadAllAsync() + await foreach = รูปแบบการบริโภคที่สะอาดที่สุด
- เชื่อมแชนเนลเป็นไปป์ไลน์เพื่อประมวลผลพร้อมกันแบบแบ่งตามขั้นตอน
- เรียก Writer.Complete() (หรือ Complete(exception)) เพื่อส่งสัญญาณว่าสตรีมสิ้นสุด
คำถามที่พบบ่อย
บทเรียน “System.Threading.Channels” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “System.Threading.Channels” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “System.Threading.Channels”
สร้างสายงานแบบผู้ผลิต-ผู้บริโภคด้วย Channel, ChannelWriter และ ChannelReader เพื่อควบคุมแรงดันย้อนกลับ คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “System.Threading.Channels” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม
ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- IAsyncEnumerable และ await foreach
- System.Threading.Channels
- ValueTask และการหลีกเลี่ยงการจัดสรรหน่วยความจำ
- ConfigureAwait และบริบทการซิงโครไนซ์