外观
线程
- Thread
- ThreadPool:特点 复用
- Task:线程池的封装
- BeginInvoke
task是线程池的封装
单核下没有多线程
线程优先级反转 由于线程是栈 先进后出
eg:A 优先级最高 ,B其次,C最低
当C占用资源时 AB 都想要拿到资源时,A先进栈 然后B 但是他们都拿不到资源 A还进入栈的最下面,所以会变成A的优先级最低 这时可以执行Task.Delay(0).Wait(); || Thread.sleep(0) 暂停0秒 也会执行将栈的下一个拿出来 所以又会变成优先级最高
异步引发并发,但不一定会出现并发,
net中分前台线程与后台线程 区别:是否让创建的所有线程都退出
- 前台线程
- 当主方法结束时,前台线程后等待
- 后台线程不会等待
说人话就是退出程序时,前台线程会阻止进程退出,后台线程会跟随程序一起销毁退出
程序(进程)会等到所有前台线程都跑完才真正退出;
后台线程跑不跑完无所谓,一旦所有前台线程结束了,后台线程会被强制终止(不等它执行完)。
new Thread(()=>{
})
{ IsBackgroud = true }.Start();ExecutionContext
如何做到在多线程中共享数据(队列、线程、工作项、await、async,task)之类的东西
NET是通过 ExecutionContext 执行上下文
执行上下文:把所有线程的本地状态放到其中,并随着所有的线程异步一起流转 理解为多元宇宙,携带同样的参数在不同的线程中穿行
本质上是一个花哨的键值对
Sleep 与 Delay 的区别
Thread.Sleep(1000); await Task.Delay(1000);
**Sleep是阻塞当前线程 会一直占用这个线程。Delay是释放当前线程回线程池,暂停执行并且交还控制权,等待时间到了再去请求线程继续执行后面的代码
Sleep让线程睡眠(等待停止),Delay内部是计时器实现的
async static Task Main(string[] args)
{
var tasks = new List<Task>();
for (int i = 0; i < 5; i++)
{
int idx = i;
tasks.Add(Task.Run(async () => // 注意这里可以是 async lambda
{
Console.WriteLine($"任务 {idx} 开始");
if (idx == 3)
{
Console.WriteLine($"线程ID:{Thread.CurrentThread.ManagedThreadId} → 睡 10 秒");
await Task.Delay(10000); // ← 异步,不卡线程
//Thread.Sleep(10000); // ← 睡眠阻塞,卡线程
Console.WriteLine($"线程ID:{Thread.CurrentThread.ManagedThreadId} → 睡醒干活");
}
else
{
await Task.Delay(500);
//Thread.Sleep(500);
}
Console.WriteLine($"任务 {idx} 完成");
}));
}
Console.WriteLine("主线程(异步)等待中...");
await Task.WhenAll(tasks); // ← 不阻塞线程池
Console.WriteLine("全部完成");
}Delay输出:
17:00:16.480 主线程(异步)等待中...
17:00:16.490 任务 2 开始
17:00:16.490 任务 4 开始
17:00:16.490 任务 3 开始
17:00:16.490 任务 1 开始
17:00:16.490 任务 0 开始
17:00:16.504 线程ID:9 → 睡 10 秒
17:00:17.020 任务 1 完成
17:00:17.020 任务 4 完成
17:00:17.020 任务 0 完成
17:00:17.020 任务 2 完成
17:00:26.512 线程ID:9 → 睡醒干活
17:00:26.512 任务 3 完成
17:00:26.512 全部完成
C:\Users\yuche\source\repos\MyWpfApps\DeepNet\bin\Debug\net8.0\DeepNet.exe (进程 9500)已退出,代码为 0 (0x0)。Sleep输出:
16:58:43.324 任务 3 开始
16:58:43.315 主线程(异步)等待中...
16:58:43.324 任务 1 开始
16:58:43.324 任务 0 开始
16:58:43.324 任务 2 开始
16:58:43.324 任务 4 开始
16:58:43.337 线程ID:10 → 睡 10 秒
16:58:43.840 任务 0 完成
16:58:43.840 任务 1 完成
16:58:43.840 任务 2 完成
16:58:43.840 任务 4 完成
16:58:53.341 线程ID:10 → 睡醒干活
16:58:53.343 任务 3 完成
16:58:53.346 全部完成
C:\Users\yuche\source\repos\MyWpfApps\DeepNet\bin\Debug\net8.0\DeepNet.exe (进程 14056)已退出,代码为 0 (0x0)。相关信息
明显可以看出delay后继续执行的线程变了,而sleep的线程没变
线程安全集合类
| 类/结构 | 主要特点 | 性能/开销考虑 |
|---|---|---|
| BlockingCollection<T> | - 阻塞式添加/取出(空/满时等待)。 - 支持有界(bounded)容量。 - CompleteAdding 机制结束生产。 - 默认底层是 ConcurrentQueue,可自定义(如用 ConcurrentStack)。 - 同步阻塞,适合简单同步场景。 | 中等(阻塞引入上下文切换开销,但线程安全)。 |
| ConcurrentQueue<T> | - 无界队列,FIFO。 - 非阻塞:TryEnqueue/TryDequeue(失败返回false)。 - 高性能,锁-free。 - 无 CompleteAdding,无内置阻塞。 | 高(锁-free,适合高吞吐)。 |
| ConcurrentStack<T> | - 无界栈,LIFO。 - 非阻塞:TryPush/TryPop。 - 类似 Queue,但后进先出。 | 高(锁-free)。 |
| ConcurrentBag<T> | - 无界、无序集合。 - 非阻塞:Add/TryTake。 - 适合并行添加/取出,无特定顺序。 | 高(工作窃取优化,适合并行)。 |
| Channel<T> (.NET 5+) | - 异步通道,支持有界/无界。 - 阻塞用 WaitToReadAsync/WaitToWriteAsync + TryRead/TryWrite。 - 支持 Complete/Reader.Completion。 - 内置异步,适合 async/await。 | 高(异步,低开销;无锁在单生产者/消费者)。 |
| BufferBlock<T> (TPL Dataflow) | - 数据流块,支持有界。 - 阻塞 Post/OfferMessage;异步 ReceiveAsync。 - 可链接其他块(如 TransformBlock),形成管道。 - 支持 Completion/衰退模式。 | 中等(TPL 调度器开销)。 |
| Queue<T> 或 Stack<T> | - 非线程安全,需手动锁 - 不推荐多线程使用 |
FIFO(Fitst In First Out) :先进先出 LIFO(Last In First Out):后尽先出
使用场景
- 简单同步生产者-消费者,需阻塞等待(后台任务填充队列,前台消费):
- 首选:BlockingCollection<T>。易用,内置阻塞和 CompleteAdding。示例:日志队列,生产者添加日志,消费者写入文件。
- 高性能、无阻塞队列(消息传递,不想阻塞线程):
- 首选:ConcurrentQueue<T>。用 Try* 方法 + 轮询或信号量。示例:游戏循环中的事件队列,高吞吐无需等。
- 异步/await 场景(Web API 或现代 app):
- 首选:Channel<T>。支持 async ReadAsync/WriteAsync,避免阻塞线程。示例:SignalR 实时消息,或流式处理数据。比 BlockingCollection 更高效(无线程池阻塞)。
- 复杂数据管道(ETL 处理:输入 → 转换 → 输出):
- 首选:BufferBlock<T> + TPL Dataflow。可链式链接块,支持并行/衰退。示例:图像处理管道,多个步骤并发。
- 无序/并行添加(多线程收集结果,无需顺序):
- 首选:ConcurrentBag<T>。示例:并行 foreach 收集数据。
- LIFO 场景(撤销栈):
- 首选:ConcurrentStack<T>,或用它包装 BlockingCollection。
