c# 多线程编程入门

来源:这里教程网 时间:2026-02-21 17:42:40 作者:

Thread.Start() 后线程不执行?检查是否调用了
Join()
或提前退出

新建的

Thread
对象调用
Start()
后没反应,常见原因是主线程快速结束,进程直接退出,子线程根本没机会运行。.NET 不会自动等待未完成的前台线程——除非你显式阻塞主线程。

默认创建的是前台线程(
IsBackground == false
),但主线程退出时整个进程终止,不管前台线程是否还在跑
别依赖“主线程 sleep 一会儿”来观察效果,这不可靠;改用
thread.Join()
等待完成
如果只是想让后台线程随主线程结束而终止,创建时设
thread.IsBackground = true
var thread = new Thread(() => {
    Console.WriteLine("子线程开始");
    Thread.Sleep(1000);
    Console.WriteLine("子线程结束");
});
thread.Start();
thread.Join(); // 必须加这一句,否则可能看不到输出

Task.Run() 比 Thread 更适合大多数场景

直接操作

Thread
容易出错:资源开销大、无法返回值、异常捕获困难、难以组合。现代 C# 应优先用
Task
async/await

Task.Run()
把工作调度到线程池,复用已有线程,避免频繁创建销毁开销
支持
await
,异常会包装进
AggregateException
,可在
try/catch
中统一处理
返回
Task<t></t>
可自然获取结果,不用手动传参或共享变量
var task = Task.Run(() => {
    Thread.Sleep(500);
    return 42;
});
int result = await task; // 直接拿到返回值

多个线程同时写同一个
List<t></t>
InvalidOperationException
或数据丢失

List<t></t>
Dictionary<k></k>
等基础集合类不是线程安全的。多线程并发 Add/Remove 会破坏内部状态,轻则抛
InvalidOperationException
,重则静默丢数据。

不要给多个线程共享一个非线程安全集合并直接操作 短期方案:用
lock
包裹所有读写操作,但容易死锁或成为性能瓶颈
长期方案:改用
ConcurrentBag<t></t>
ConcurrentQueue<t></t>
ConcurrentDictionary<k></k>
注意:
ConcurrentDictionary
GetOrAdd
是原子的,但
ContainsKey + Add
组合不是

async 方法里用
Thread.Sleep()
会阻塞线程,改用
Task.Delay()

async
方法中误用
Thread.Sleep(1000)
,会导致当前线程被白白占用 1 秒,违背异步初衷;尤其在 ASP.NET Core 等受限线程池环境中,会显著降低吞吐量。

Thread.Sleep()
是同步阻塞,线程挂起但不释放
Task.Delay(1000)
是真正的异步等待,不占用线程,到期后由线程池回调继续执行
数据库查询、HTTP 调用等 I/O 操作本身已是异步(如
HttpClient.GetAsync
),无需也不该再套
Task.Run
public async Task DoWorkAsync()
{
    await Task.Delay(1000); // ✅ 正确
    // await Task.Run(() => Thread.Sleep(1000)); // ❌ 错误:把异步包装成伪异步
}

线程安全和异步语义的混淆是最难调试的问题之一——比如以为

async void
可以安全触发后台任务,实际异常会直接崩掉进程;又比如在
lock
块里调用
await
,编译器会报错,但有人硬拆成两段就埋下竞态。这些点不写具体代码很难意识到,但一旦发生,现象往往非常随机。

相关推荐