多线程与委托
一、多线程基础概念
核心概念
- 线程(Thread) - 程序执行的最小单元,每个进程可包含多个线程
- 主线程 - 程序启动时自动创建的线程,负责运行 Main 方法
- 多线程 - 同时执行多个线程,提高 CPU 利用率和程序响应性
- 线程安全 - 多个线程访问共享资源时不产生数据竞争或不一致
二、实现多线程的方式
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();异步方法的规则
- ✅ 方法名以
Async结尾 - ✅ 返回类型为
Task或Task<T> - ✅ 使用
await关键字等待异步操作 - ❌ 避免
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); // 83. 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.Run | Thread.Start |
|---|---|---|
| 抽象级别 | 高级(TPL) | 低级 |
| 线程池 | 自动使用 | 手动创建 |
| 返回值 | 支持 | 不支持 |
| 异步支持 | 支持 async/await | 不支持 |
| 推荐度 | ✅ 推荐 | ❌ 不推荐 |
Q2: async/await 会创建新线程吗?
不一定!
await只是挂起当前方法,释放线程- 如果是 I/O 操作(如网络请求),不会创建新线程
- 如果是 CPU 密集操作,需要配合
Task.Run使用新线程
Q3: 如何避免死锁?
- ✅ 避免嵌套锁
- ✅ 使用
async/await代替.Result - ✅ 使用
ConfigureAwait(false)(库代码中) - ✅ 统一加锁顺序
Q4: 委托和事件的区别?
| 特性 | 委托 | 事件 |
|---|---|---|
| 访问权限 | 可以在外部调用 | 只能在类内部触发 |
| 赋值 | 可以直接赋值 = | 只能 += 或 -= |
| 封装性 | 弱 | 强 |
| 使用场景 | 回调方法 | 发布-订阅模式 |
csharp
// 委托
public delegate void MyDelegate();
public MyDelegate myDelegate; // 外部可以直接调用
// 事件
public event MyDelegate myEvent; // 外部只能订阅,不能触发七、最佳实践
- ✅ 优先使用
Task和async/await - ✅ CPU 密集型任务使用
Task.Run - ✅ I/O 密集型任务使用
async方法 - ✅ 避免在构造函数中使用
async - ✅ 使用
CancellationToken支持取消操作 - ✅ 使用线程安全的集合(如
ConcurrentDictionary) - ❌ 避免
Task.Wait()和.Result(可能死锁) - ❌ 避免
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);