在 .NET环境下,多线程编程主要有 Thread ThreadPool Task Parallel BackgroundWorker 等几种,还有一个与多线程相关的:异步编程 async/await ,值得强调的是,异步编程不等于多线程 。当然,这几种多线程编程的方式并不是独立开的,在底层的封装有一定的联系。
1. Thread
1.1 基础应用
引用Thread的命名空间
using System;
using System.Threading;
直接创建线程进行启动
static void Main()
{
// 创建一个新线程
Thread thread = new Thread(DoWork);
thread.Start(); // 启动线程
// 主线程继续执行
for (int i = 0; i < 10; i++)
{
Console.WriteLine("这是主线程打印: " + i);
Thread.Sleep(500);
}
Console.ReadKey();
}
static void DoWork()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("这是自主创建的线程打印: " + i);
Thread.Sleep(500);
}
}
调试结果:
这是自主创建的线程打印: 0
这是主线程打印: 0
这是自主创建的线程打印: 1
这是主线程打印: 1
这是主线程打印: 2
这是自主创建的线程打印: 2
这是主线程打印: 3
这是自主创建的线程打印: 3
这是主线程打印: 4
这是自主创建的线程打印: 4
这是主线程打印: 5
这是自主创建的线程打印: 5
这是主线程打印: 6
这是自主创建的线程打印: 6
这是主线程打印: 7
这是自主创建的线程打印: 7
这是主线程打印: 8
这是自主创建的线程打印: 8
这是主线程打印: 9
这是自主创建的线程打印: 9
可以发现,主线程和自己创建的线程各干各的,互不耽搁。
1.2 线程入参
如果我在启动线程时想给他一个参数呢?这样搞:
1. 在委托方法设置参数
static void Main()
{
// 创建一个新线程
Thread thread = new Thread(DoWork);
thread.Start("子线程启动啦"); // 启动线程
// 主线程继续执行
for (int i = 0; i < 10; i++)
{
Console.WriteLine("这是主线程打印: " + i);
Thread.Sleep(500);
}
Console.ReadKey();
}
static void DoWork(object obj)
{
// 将传入的 object 类型参数转换为实际类型
string message = (string)obj;
Console.WriteLine($"Received message in Main thread: {message}");
for (int i = 0; i < 10; i++)
{
Console.WriteLine("这是自主创建的线程打印: " + i);
Thread.Sleep(500);
}
}
调试结果:
这是主线程打印: 0
Received message in Main thread: 子线程启动啦
这是自主创建的线程打印: 0
这是自主创建的线程打印: 1
这是主线程打印: 1
这是主线程打印: 2
这是自主创建的线程打印: 2
这是主线程打印: 3
这是自主创建的线程打印: 3
这是主线程打印: 4
这是自主创建的线程打印: 4
这是主线程打印: 5
这是自主创建的线程打印: 5
这是主线程打印: 6
这是自主创建的线程打印: 6
这是主线程打印: 7
这是自主创建的线程打印: 7
这是自主创建的线程打印: 8
这是主线程打印: 8
这是主线程打印: 9
这是自主创建的线程打印: 9
2. 使用Lambda表达式
static void Main()
{
// 创建一个新线程
// 使用 Lambda 表达式创建线程并传递参数
Thread thread = new Thread(() =>
{
DoWork("子线程又又又启动啦");
});
// 启动线程
thread.Start();
// 主线程继续执行
for (int i = 0; i < 10; i++)
{
Console.WriteLine("这是主线程打印: " + i);
Thread.Sleep(500);
}
Console.ReadKey();
}
static void DoWork(object obj)
{
// 将传入的 object 类型参数转换为实际类型
string message = (string)obj;
Console.WriteLine($"Received message in Main thread: {message}");
for (int i = 0; i < 10; i++)
{
Console.WriteLine("这是自主创建的线程打印: " + i);
Thread.Sleep(500);
}
}
调试运行结果:
这是主线程打印: 0
Received message in Main thread: 子线程又又又启动啦
这是自主创建的线程打印: 0
这是主线程打印: 1
这是自主创建的线程打印: 1
这是主线程打印: 2
这是自主创建的线程打印: 2
这是主线程打印: 3
这是自主创建的线程打印: 3
这是主线程打印: 4
这是自主创建的线程打印: 4
这是主线程打印: 5
这是自主创建的线程打印: 5
这是主线程打印: 6
这是自主创建的线程打印: 6
这是主线程打印: 7
这是自主创建的线程打印: 7
这是主线程打印: 8
这是自主创建的线程打印: 8
这是主线程打印: 9
这是自主创建的线程打印: 9
2. ThreadPool
System.Threading.ThreadPool
是一个线程池,用于管理和复用线程,适合处理大量短生命周期的任务。其实这个是对Thread的封装,从某种程度上讲节省了程序运行的开销。
static void Main()
{
// 将任务加入线程池
ThreadPool.QueueUserWorkItem(DoWork);
// 主线程继续执行
for (int i = 0; i < 10; i++)
{
Console.WriteLine("这是主线程打印: " + i);
Thread.Sleep(500);
}
Console.ReadKey();
}
static void DoWork(object obj)
{
// 将传入的 object 类型参数转换为实际类型
string message = (string)obj;
Console.WriteLine($"Received message in Main thread: {message}");
for (int i = 0; i < 10; i++)
{
Console.WriteLine("这是自主创建的线程打印: " + i);
Thread.Sleep(500);
}
}
运行调试结果
这是主线程打印: 0
Received message in Main thread:
这是自主创建的线程打印: 0
这是自主创建的线程打印: 1
这是主线程打印: 1
这是主线程打印: 2
这是自主创建的线程打印: 2
这是主线程打印: 3
这是自主创建的线程打印: 3
这是主线程打印: 4
这是自主创建的线程打印: 4
这是主线程打印: 5
这是自主创建的线程打印: 5
3. Task
System.Threading.Tasks.Task
是基于任务的异步编程模型(TAP),是对ThreadPool
的进一步封装,提供了更高级的功能。感觉套娃是微软的强项哈哈。
static void Main()
{
// 创建一个任务并启动
Task task = Task.Run(() => DoWork());
// 主线程继续执行
for (int i = 0; i < 4; i++)
{
Console.WriteLine("这是主线程打印: " + i);
Task.Delay(500).Wait();
}
task.Wait(); // 等待任务完成
Console.ReadKey();
}
static void DoWork()
{
for (int i = 0; i < 7; i++)
{
Console.WriteLine("这是自主创建的线程打印: " + i);
Thread.Sleep(500);
}
}
调试执行:
这是主线程打印: 0
这是自主创建的线程打印: 0
这是自主创建的线程打印: 1
这是主线程打印: 1
这是自主创建的线程打印: 2
这是主线程打印: 2
这是主线程打印: 3
这是自主创建的线程打印: 3
这是自主创建的线程打印: 4
这是自主创建的线程打印: 5
这是自主创建的线程打印: 6
从综合性能和实战经验来看,Task应该是应用最多的,主要体现在以下几个特点
支持任务并行和异步操作。
可以获取任务的执行结果(通过
Task<TResult>
)。支持任务取消、延续和异常处理。
4. Parallel
System.Threading.Tasks.Parallel
提供了简单的并行循环和并行任务执行。
// 并行执行循环
Parallel.For(0, 5, i =>
{
Console.WriteLine("Parallel thread: " + i);
Task.Delay(500).Wait();
});
调试运行:
Parallel thread: 3
Parallel thread: 0
Parallel thread: 4
Parallel thread: 1
Parallel thread: 2
简化并行循环的编写。
自动管理线程池中的线程。
这种模式适用于任务并行分发的场景,简单,高效。
5. BackgroundWorker
System.ComponentModel.BackgroundWorker
是一个用于在后台执行任务的组件,适合在UI应用程序中使用。
代码示例:
static void Main()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWork;
worker.RunWorkerCompleted += WorkCompleted;
worker.RunWorkerAsync(); // 启动后台任务
Console.WriteLine("Main thread continues...");
Console.ReadKey();
}
static void DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("工作线程: " + i);
System.Threading.Thread.Sleep(500);
}
}
static void WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("任务完成.");
}
运行结果:
Main thread continues...
工作线程: 0
工作线程: 1
工作线程: 2
工作线程: 3
工作线程: 4
任务完成.
6. async/await
async/await不是多线程。只是和多线程相关,具体来说是和Task直接相关。
代码示例1:
static async Task Main()
{
Console.WriteLine("主线程启动.");
// 异步调用
await DoWorkAsync();
Console.WriteLine("主线程完成.");
Console.ReadKey();
}
static async Task DoWorkAsync()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("异步方法执行: " + i);
await Task.Delay(500);
}
}
运行结果:
主线程启动.
异步方法执行: 0
异步方法执行: 1
异步方法执行: 2
异步方法执行: 3
异步方法执行: 4
主线程完成.
或者这样写:
static async Task DoWorkAsync()
{
for (int i = 0; i < 5; i++)
{
await Task.Run(() =>
{
Console.WriteLine("异步方法执行: " + i);
Task.Delay(500);
});
}
}
关于异步编程,在杨中科的书里学了一点:第2章 .NETCore异步编程 C#异步编程 关键字async/await用法
DeepSeek总结:
种类 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
Thread | 需要精细控制线程的场景 | 直接控制线程生命周期 | 手动管理线程,开销较大 |
ThreadPool | 大量短生命周期任务 | 线程复用,减少开销 | 不适合长时间任务 |
Task | 异步编程和任务并行 | 功能强大,支持任务结果和异常处理 | 需要理解异步编程模型 |
Parallel | 简单的并行循环 | 简化并行编程 | 不适合复杂任务 |
async/await | 异步编程,避免阻塞主线程 | 代码简洁,易于理解 | 需要理解异步编程模型 |
BackgroundWorker | UI应用程序中的后台任务 | 支持进度报告和取消操作 | 功能相对简单 |