Skip to content

多线程与委托

一、多线程基础概念

核心概念

  1. 线程(Thread) - 程序执行的最小单元,每个进程可包含多个线程
  2. 主线程 - 程序启动时自动创建的线程,负责运行 Main 方法
  3. 多线程 - 同时执行多个线程,提高 CPU 利用率和程序响应性
  4. 线程安全 - 多个线程访问共享资源时不产生数据竞争或不一致

二、实现多线程的方式

1. Thread 类(原始方式)

适用场景: 简单的后台任务
缺点: 手动管理、无法返回值、资源开销大

csharp
Thread thread = new Thread(() =>
{
    Console.WriteLine("后台线程执行");
});
thread.Start();
thread.Join(); // 等待线程完成

2. ThreadPool(线程池)

优点: 复用线程,减少创建/销毁开销
缺点: 无法控制执行顺序,无法取消任务

csharp
ThreadPool.QueueUserWorkItem(state =>
{
    Console.WriteLine("线程池任务执行");
});

3. Task(推荐)

优点:

  • 更高层级的抽象
  • 支持 async/await
  • 可返回结果、支持异常传播
  • 自动使用线程池
csharp
// 无返回值
Task task = Task.Run(() =>
{
    Console.WriteLine("任务在后台线程执行");
});

// 带返回值
Task<int> resultTask = Task.Run(() =>
{
    return 42;
});
int result = await resultTask;

三、async/await 异步编程

基本用法

csharp
public async Task<string> GetDataAsync()
{
    await Task.Delay(1000); // 模拟异步操作
    return "数据";
}

// 调用
string data = await GetDataAsync();

异步方法的规则

  1. ✅ 方法名以 Async 结尾
  2. ✅ 返回类型为 TaskTask<T>
  3. ✅ 使用 await 关键字等待异步操作
  4. ❌ 避免 async void(除了事件处理)

常见陷阱

csharp
// ❌ 错误:阻塞等待
var result = GetDataAsync().Result; // 可能死锁

// ✅ 正确:异步等待
var result = await GetDataAsync();

四、委托(Delegate)

什么是委托?

委托是类型安全的函数指针,可以引用方法并传递方法。

委托的定义和使用

csharp
// 定义委托
public delegate int Calculate(int a, int b);

// 创建委托实例
Calculate add = (a, b) => a + b;
Calculate multiply = (a, b) => a * b;

// 调用委托
int sum = add(5, 3);        // 8
int product = multiply(5, 3); // 15

内置委托类型

1. Action(无返回值)

csharp
Action<string> print = message => Console.WriteLine(message);
print("Hello World");

2. Func(有返回值)

csharp
Func<int, int, int> add = (a, b) => a + b;
int result = add(5, 3); // 8

3. Predicate(返回 bool)

csharp
Predicate<int> isEven = n => n % 2 == 0;
bool result = isEven(4); // true

多播委托

csharp
Action action = () => Console.WriteLine("方法1");
action += () => Console.WriteLine("方法2");
action += () => Console.WriteLine("方法3");

action(); // 依次执行所有方法

五、线程安全与同步

1. lock 语句

csharp
private static object _lock = new object();
private static int _counter = 0;

public void Increment()
{
    lock (_lock)
    {
        _counter++;
    }
}

2. Monitor

csharp
Monitor.Enter(_lock);
try
{
    _counter++;
}
finally
{
    Monitor.Exit(_lock);
}

3. Interlocked(原子操作)

csharp
int counter = 0;
Interlocked.Increment(ref counter);

4. Semaphore(信号量)

csharp
SemaphoreSlim semaphore = new SemaphoreSlim(3); // 最多3个线程

await semaphore.WaitAsync();
try
{
    // 执行操作
}
finally
{
    semaphore.Release();
}

六、常见面试题

Q1: Task.Run 和 Thread.Start 的区别?

特性Task.RunThread.Start
抽象级别高级(TPL)低级
线程池自动使用手动创建
返回值支持不支持
异步支持支持 async/await不支持
推荐度✅ 推荐❌ 不推荐

Q2: async/await 会创建新线程吗?

不一定!

  • await 只是挂起当前方法,释放线程
  • 如果是 I/O 操作(如网络请求),不会创建新线程
  • 如果是 CPU 密集操作,需要配合 Task.Run 使用新线程

Q3: 如何避免死锁?

  1. ✅ 避免嵌套锁
  2. ✅ 使用 async/await 代替 .Result
  3. ✅ 使用 ConfigureAwait(false)(库代码中)
  4. ✅ 统一加锁顺序

Q4: 委托和事件的区别?

特性委托事件
访问权限可以在外部调用只能在类内部触发
赋值可以直接赋值 =只能 +=-=
封装性
使用场景回调方法发布-订阅模式
csharp
// 委托
public delegate void MyDelegate();
public MyDelegate myDelegate; // 外部可以直接调用

// 事件
public event MyDelegate myEvent; // 外部只能订阅,不能触发

七、最佳实践

  1. ✅ 优先使用 Taskasync/await
  2. ✅ CPU 密集型任务使用 Task.Run
  3. ✅ I/O 密集型任务使用 async 方法
  4. ✅ 避免在构造函数中使用 async
  5. ✅ 使用 CancellationToken 支持取消操作
  6. ✅ 使用线程安全的集合(如 ConcurrentDictionary
  7. ❌ 避免 Task.Wait().Result(可能死锁)
  8. ❌ 避免 async void(除了事件处理)

八、代码示例:实际应用

并发下载多个文件

csharp
public async Task DownloadFilesAsync(List<string> urls)
{
    var tasks = urls.Select(url => DownloadFileAsync(url));
    await Task.WhenAll(tasks); // 等待所有任务完成
}

private async Task DownloadFileAsync(string url)
{
    using var client = new HttpClient();
    var content = await client.GetStringAsync(url);
    // 处理下载的内容
}

生产者-消费者模式

csharp
Channel<int> channel = Channel.CreateUnbounded<int>();

// 生产者
Task producer = Task.Run(async () =>
{
    for (int i = 0; i < 100; i++)
    {
        await channel.Writer.WriteAsync(i);
        await Task.Delay(10);
    }
    channel.Writer.Complete();
});

// 消费者
Task consumer = Task.Run(async () =>
{
    await foreach (var item in channel.Reader.ReadAllAsync())
    {
        Console.WriteLine($"消费: {item}");
    }
});

await Task.WhenAll(producer, consumer);

基于 VitePress 构建 | Copyright © 2026-present