C# 多线程

`Thread`、`Task`、`async/await`、`lock`、以及并发集合。

Advanced
Key Points现代 C# 用 `Task` + `async/await`,几乎不用裸 `Thread`。

Task 与 async/await

C#
async Task<string> FetchAsync(string name, int ms)
{
    await Task.Delay(ms);
    return $"{name} 完成";
}

var sw = System.Diagnostics.Stopwatch.StartNew();

// 串行
var a = await FetchAsync("A", 100);
var b = await FetchAsync("B", 100);
Console.WriteLine($"{a} {b} 串行 {sw.ElapsedMilliseconds}ms");

sw.Restart();

// 并发
var t1 = FetchAsync("A", 100);
var t2 = FetchAsync("B", 100);
var results = await Task.WhenAll(t1, t2);
Console.WriteLine($"{string.Join(" ", results)} 并发 {sw.ElapsedMilliseconds}ms");
Output
A 完成 B 完成 串行 207ms
A 完成 B 完成 并发 105ms

Start the tasks, then await to get concurrency. await FetchAsync(...) waits immediately so the next never starts — the classic "I thought this was concurrent".

Task 常用 API

C#
// 全部完成
var all = await Task.WhenAll(
    Task.FromResult(1),
    Task.FromResult(2)
);
Console.WriteLine(string.Join(",", all));

// 任一完成
var first = await Task.WhenAny(
    Task.Delay(50).ContinueWith(_ => "快"),
    Task.Delay(200).ContinueWith(_ => "慢")
);
Console.WriteLine(first.Result);

// 起一个后台任务
var t = Task.Run(() =>
{
    int sum = 0;
    for (int i = 0; i < 1000; i++) sum += i;
    return sum;
});
Console.WriteLine(await t);

// 超时
using var cts = new CancellationTokenSource(100);
try
{
    await Task.Delay(1000, cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("超时取消");
}
Output
1,2
快
499500
超时取消
APIMeaning
Task.Run在线程池跑 CPU 任务
Task.Delay异步等待(不阻塞线程)
Task.WhenAll等全部完成
Task.WhenAny等任一完成
CancellationToken取消信号
Task.FromResult包装已有值

lock 与共享状态

C#
class Counter
{
    private int _n;
    private readonly object _gate = new();

    public void Inc()
    {
        lock (_gate)
        {
            _n++;
        }
    }

    public int Value { get { lock (_gate) return _n; } }
}

var c = new Counter();
await Task.WhenAll(Enumerable.Range(0, 1000).Select(_ => Task.Run(() => c.Inc())));
Console.WriteLine(c.Value);
Output
1000

lock uses Monitor: one thread at a time. The lock object must be private, a reference type, and not locked elsewhere — lock(this) or lock(typeof(X)) can deadlock.

并发集合

C#
using System.Collections.Concurrent;

var bag = new ConcurrentBag<int>();
var dict = new ConcurrentDictionary<string, int>();
var queue = new ConcurrentQueue<int>();

await Task.WhenAll(Enumerable.Range(0, 100).Select(i => Task.Run(() =>
{
    bag.Add(i);
    dict.AddOrUpdate("count", 1, (_, old) => old + 1);
    queue.Enqueue(i);
})));

Console.WriteLine(bag.Count);
Console.WriteLine(dict["count"]);
Console.WriteLine(queue.Count);
Output
100
100
100
CollectionUse
ConcurrentDictionary并发键值缓存
ConcurrentQueue生产者消费者队列
ConcurrentBag无序收集
BlockingCollection有界队列,阻塞式
Channel<T>现代异步生产者消费者
ConcurrentDictionary's AddOrUpdate/GetOrAdd are atomic, but the delegate may run more than once under contention. Keep delegates side-effect free.

async 的三条铁律

C#
// 1. 不要 async void(除了事件处理器)——异常无法捕获
// async void Bad() { await Task.Delay(1); throw new Exception(); }

// 2. 不要 .Result / .Wait()——会阻塞线程,可能死锁
// var x = FetchAsync().Result;      // 危险

// 3. 一路 async 到底(async all the way)
async Task<int> GetAsync() => await Task.FromResult(1);
async Task RunAsync() => Console.WriteLine(await GetAsync());

await RunAsync();

// 4. 库代码用 ConfigureAwait(false) 避免回到原上下文
async Task LibAsync()
{
    await Task.Delay(1).ConfigureAwait(false);
}
await LibAsync();
Output
1

.Result deadlocks on a UI thread or classic ASP.NET context: the main thread blocks waiting while the await continuation needs that same thread. The classic C# async trap.

选哪个

NeedUse
I/O 等待(HTTP、数据库、文件)async/await
CPU 密集计算Task.Run 或 Parallel.For
定时后台任务BackgroundService / PeriodicTimer
极细粒度控制线程Thread(很少用)
大量数据并行Parallel.ForEach / PLINQ

Friends & Partners