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,编译器会报错,但有人硬拆成两段就埋下竞态。这些点不写具体代码很难意识到,但一旦发生,现象往往非常随机。
