《C#多线程编程》
在.NET开发中,多线程编程是提升程序性能、优化资源利用的核心技术。通过并行执行任务,开发者可以充分利用多核处理器的计算能力,解决耗时操作(如网络请求、文件I/O、复杂计算)导致的界面卡顿问题。本文将系统讲解C#中的多线程编程模型,涵盖基础线程操作、线程池、异步编程模型(APM/EAP/TAP)、并行扩展(PLINQ/Parallel类)以及线程同步机制,并结合实际案例说明如何安全高效地实现多线程应用。
一、基础线程操作
C#通过`System.Threading`命名空间提供基础的线程支持。创建线程最直接的方式是实例化`Thread`类并传入方法委托。
using System;
using System.Threading;
class Program
{
static void WorkerMethod(object data)
{
Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 开始工作,参数: {data}");
Thread.Sleep(2000); // 模拟耗时操作
Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 完成工作");
}
static void Main()
{
Console.WriteLine($"主线程ID: {Thread.CurrentThread.ManagedThreadId}");
// 创建线程并传递参数
Thread thread = new Thread(WorkerMethod);
thread.Start("测试数据");
// 主线程继续执行
Console.WriteLine("主线程继续执行其他任务...");
thread.Join(); // 等待子线程结束
Console.WriteLine("所有线程执行完毕");
}
}
上述代码展示了线程的创建、启动和等待。但直接使用`Thread`类存在以下问题:线程创建开销大、难以管理大量线程、缺乏统一的资源回收机制。因此,实际应用中更推荐使用线程池。
二、线程池(ThreadPool)
线程池通过复用已创建的线程来减少线程管理的开销。.NET的线程池会自动调整线程数量,根据系统负载动态分配资源。
using System;
using System.Threading;
class Program
{
static void TaskMethod(object state)
{
Console.WriteLine($"线程池线程 {Thread.CurrentThread.ManagedThreadId} 处理任务,状态: {state}");
Thread.Sleep(1000);
}
static void Main()
{
// 向线程池排队任务
for (int i = 0; i
线程池适用于短时间、高频率的任务执行,但存在以下限制:无法取消已排队的任务、无法设置线程优先级、难以追踪单个任务的执行状态。对于需要更精细控制的场景,异步编程模型(APM)和任务并行库(TPL)是更好的选择。
三、异步编程模型(APM/EAP/TAP)
.NET提供了三种异步编程模式,其中基于任务的异步模式(TAP)是现代C#开发的首选方案。
1. 传统APM模式(IAsyncResult)
APM通过`BeginXxx`/`EndXxx`方法对实现异步操作,例如文件读取:
using System;
using System.IO;
using System.Threading;
class Program
{
static void Main()
{
FileStream fs = new FileStream("test.txt", FileMode.Open);
byte[] buffer = new byte[fs.Length];
// 开始异步读取
IAsyncResult result = fs.BeginRead(buffer, 0, buffer.Length, null, null);
// 主线程可执行其他操作
Console.WriteLine("主线程继续执行...");
// 等待异步操作完成
while (!result.IsCompleted)
{
Thread.Sleep(100);
}
// 结束异步操作
int bytesRead = fs.EndRead(result);
Console.WriteLine($"读取了 {bytesRead} 字节");
fs.Close();
}
}
APM模式需要手动管理状态对象和回调,代码复杂度较高。
2. 基于事件的异步模式(EAP)
EAP通过事件通知异步操作完成,典型代表是`WebClient`类:
using System;
using System.Net;
class Program
{
static void Main()
{
WebClient client = new WebClient();
client.DownloadStringCompleted += (sender, e) =>
{
if (e.Error == null)
{
Console.WriteLine($"下载内容长度: {e.Result.Length}");
}
else
{
Console.WriteLine($"下载错误: {e.Error.Message}");
}
};
Console.WriteLine("开始下载...");
client.DownloadStringAsync(new Uri("https://example.com"));
// 防止主线程退出
Console.ReadLine();
}
}
EAP简化了异步编程,但事件驱动的模型在复杂场景下仍显繁琐。
3. 基于任务的异步模式(TAP)
TAP是.NET 4.0引入的现代异步编程模型,通过`async`/`await`关键字实现简洁的异步代码:
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task DownloadFileAsync(string url, string savePath)
{
using (HttpClient client = new HttpClient())
{
Console.WriteLine("开始下载...");
byte[] data = await client.GetByteArrayAsync(url);
Console.WriteLine($"下载完成,数据大小: {data.Length}");
await File.WriteAllBytesAsync(savePath, data);
Console.WriteLine("文件保存成功");
}
}
static async Task Main()
{
try
{
await DownloadFileAsync(
"https://example.com/sample.txt",
"downloaded.txt");
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
}
}
}
TAP的优势在于:
- 代码结构与同步代码类似,易于维护
- 自动处理线程上下文切换
- 支持异常传播和取消操作
- 与异步流(IAsyncEnumerable)无缝集成
四、并行扩展(PLINQ与Parallel类)
对于数据并行场景,.NET提供了PLINQ(Parallel LINQ)和`Parallel`类来简化多线程编程。
1. PLINQ示例
using System;
using System.Linq;
class Program
{
static void Main()
{
int[] numbers = Enumerable.Range(1, 1000).ToArray();
// 顺序处理
var sequentialSum = numbers.Where(n => n % 2 == 0).Sum();
// 并行处理
var parallelSum = numbers.AsParallel()
.Where(n => n % 2 == 0)
.Sum();
Console.WriteLine($"顺序计算结果: {sequentialSum}");
Console.WriteLine($"并行计算结果: {parallelSum}");
}
}
PLINQ会自动将查询分解为多个任务并在多个线程上执行,但需要注意线程安全问题。
2. Parallel类示例
using System;
using System.Threading.Tasks;
class Program
{
static void ProcessItem(int item)
{
Console.WriteLine($"处理项目 {item} 在线程 {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(100); // 模拟处理时间
}
static void Main()
{
ParallelOptions options = new ParallelOptions
{
MaxDegreeOfParallelism = 4 // 限制最大并发数
};
Parallel.For(0, 20, options, i =>
{
ProcessItem(i);
});
Console.WriteLine("所有项目处理完成");
}
}
`Parallel.For`和`Parallel.ForEach`提供了声明式的并行循环,支持取消令牌和分区控制。
五、线程同步机制
多线程编程的核心挑战是共享资源的同步访问。.NET提供了多种同步原语:
1. lock关键字
using System;
using System.Threading;
class Counter
{
private int _count = 0;
private readonly object _lockObj = new object();
public void Increment()
{
lock (_lockObj)
{
_count++;
Thread.Sleep(10); // 模拟耗时操作
}
}
public int Value => _count;
}
class Program
{
static void Main()
{
Counter counter = new Counter();
Parallel.For(0, 100, _ =>
{
counter.Increment();
});
Console.WriteLine($"最终计数: {counter.Value}");
}
}
`lock`语句通过获取指定对象的互斥锁来确保代码块的原子性执行。
2. Monitor类
`Monitor`提供了比`lock`更底层的控制,支持尝试获取锁和超时机制:
using System;
using System.Threading;
class ResourcePool
{
private int _available = 5;
private readonly object _poolLock = new object();
public bool TryAcquire(out int resourceId)
{
if (!Monitor.TryEnter(_poolLock, TimeSpan.FromSeconds(1)))
{
resourceId = -1;
return false;
}
try
{
if (_available > 0)
{
resourceId = _available--;
return true;
}
resourceId = -1;
return false;
}
finally
{
Monitor.Exit(_poolLock);
}
}
}
class Program
{
static void Main()
{
ResourcePool pool = new ResourcePool();
Parallel.For(0, 10, _ =>
{
if (pool.TryAcquire(out int id))
{
Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 获取资源 {id}");
Thread.Sleep(100);
}
else
{
Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 获取资源失败");
}
});
}
}
3. 信号量(Semaphore)
信号量控制同时访问资源的线程数量:
using System;
using System.Threading;
class ConnectionPool
{
private Semaphore _semaphore;
private int _maxConnections;
public ConnectionPool(int maxConnections)
{
_maxConnections = maxConnections;
_semaphore = new Semaphore(maxConnections, maxConnections);
}
public void UseConnection(Action action)
{
_semaphore.WaitOne(); // 获取信号量
try
{
Console.WriteLine($"获取连接(剩余{_semaphore.AvailablePermits})");
action();
}
finally
{
_semaphore.Release(); // 释放信号量
}
}
}
class Program
{
static void Main()
{
ConnectionPool pool = new ConnectionPool(3);
Parallel.For(0, 10, _ =>
{
pool.UseConnection(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 使用连接");
});
});
}
}
4. 互斥量(Mutex)
互斥量可用于跨进程同步:
using System;
using System.Threading;
class Program
{
static void Main()
{
bool createdNew;
using (Mutex mutex = new Mutex(true, "Global\\MyAppMutex", out createdNew))
{
if (!createdNew)
{
Console.WriteLine("程序已在运行中");
return;
}
Console.WriteLine("程序正常运行");
Console.ReadLine();
}
}
}
六、最佳实践与常见陷阱
1. 避免过度同步:只在必要时使用同步机制,过多的锁会降低性能
2. 优先使用高级抽象:TPL、PLINQ等通常比手动线程管理更高效
3. 注意线程安全:共享可变状态时必须同步,或使用不可变对象
4. 正确处理异常:异步代码中的异常需要通过`await`传播或`try/catch`捕获
5. 避免死锁:确保锁的获取顺序一致,防止循环等待
6. 考虑任务取消:使用`CancellationToken`实现优雅的任务终止
多线程编程是.NET开发者必须掌握的核心技能。从基础的`Thread`类到现代的`async/await`模式,从简单的锁机制到复杂的并行算法,.NET提供了丰富的工具集来应对各种并发场景。合理运用这些技术可以显著提升应用程序的响应能力和吞吐量,但同时也需要谨慎处理同步问题以避免竞态条件和死锁。随着.NET的持续演进,特别是.NET Core/.NET 5+对跨平台并发模型的支持,多线程编程将继续在高性能计算、实时系统等领域发挥关键作用。
关键词:C#多线程编程、.NET线程池、异步编程、TAP模式、PLINQ、Parallel类、线程同步、lock关键字、信号量、互斥量
简介:本文系统讲解C#多线程编程技术,涵盖基础线程操作、线程池、异步编程模型(APM/EAP/TAP)、并行扩展(PLINQ/Parallel类)以及线程同步机制。通过代码示例说明如何安全高效地实现多线程应用,并总结了最佳实践与常见陷阱。