C# :多线程入门
1. 理解进程、线程和任务
进程拥有资源,比如分配给进程的内存和线程。
线程一条一条地执行代码。默认情况下,每个进程只有一个线程。
现代操作系统使用了抢夺式多任务处理,从而模拟了任务的并行执行。
如果有少量的复杂工作要做,并且希望完全控制它们,那么可以使用Thread实例。如果有一个主线程和多个可以在后台执行的工作块,那么可以使用ThreadPool类添加一些委托实例,这些委托实例指向作为队列方法实现的工作做块,并且将它们自动分配给线程池中的线程。
2. 异步运行任务
创建一个.NET 6.0 控制台应用。
2.1 同步执行多个操作
在Program.cs中添加两个名称空间:
using System.Diagnostics;
using static System.Console;
添加几个静态方法:
static void OutputThreadInfo()
{
Thread t = Thread.CurrentThread;
WriteLine(
"Thread ID:{0},Priority:{1},Background:{2},Name:{3}",
t.ManagedThreadId,
t.Priority,
t.IsBackground,
t.Name ?? "null"
);
}
static void MethodA()
{
WriteLine("启动 MethodA...");
OutputThreadInfo();
Thread.Sleep(3000);
WriteLine("完成 MethodA");
}
static void MethodB()
{
WriteLine("启动 MethodB...");
OutputThreadInfo();
Thread.Sleep(2000);
WriteLine("完成 MethodB");
}
static void MethodC()
{
WriteLine("启动 MethodC...");
OutputThreadInfo();
Thread.Sleep(1000);
WriteLine("完成 MethodC");
}
在Program.cs的顶部添加语句,来调用该方法,输出关于线程的信息,定义并启动一个秒表,调用三个方法,然后输出经过的毫秒数:
OutputThreadInfo();
Stopwatch timer = Stopwatch.StartNew();
//同步执行多个操作
WriteLine("在一个thread中同步地运行方法");
MethodA();
MethodB();
MethodC();
执行结果:
2.2 使用任务异步执行多个操作
Thread类从.NET第一个版本就存在了,但是直接使用比较麻烦。
.NET Framework 4.0 在2010年引入了Task类。Task类是线程的封装器,允许更容易的创建和管理线程。通过管理任务中封装的多个线程,可以实现代码的异步执行。
每个Task实例都有Status和CreationOptions属性,还有ContinueWith方法;该方法可以使用TaskContinuationOptions枚举进行自定义,也可以使用TaskFactory类进行管理。
启动任务
注释掉上面的同步执行代码,添加如下代码:
//使用任务异步执行多个操作
WriteLine("在多个thread中运行方法");
Task taskA = new(MethodA); taskA.Start();
Task taskB = Task.Factory.StartNew(MethodB);
Task taskC = Task.Run(MethodC);
执行结果如下:
这三个方法都由线程池中分配的三个新的后台工作线程执行。
2.3 等待任务
方法列表:
方法 | 说明 |
---|---|
t.Wait() | 等待名为t的Task实例完成执行 |
Task.WaitAny(Task[]) | 等待数组中的任何任务完成执行 |
Task.WaitAll(Task[]) | 等待数组中的所有任务完成执行 |
对任务使用Wait方法
保留上面的代码,并在下面添加如下代码,注意添加在输出运行时间的那行代码之前:
//等待任务
Task[] tasks = { taskA, taskB, taskC };
Task.WaitAll(tasks);
执行结果:
可以看到,三个方法同步执行,MethodC最先完成,因为它需要的时间最短。总体时间被运行时间最长的MethodA影响。
2.4 继续执行另一任务
如果一个任务依赖另一个任务的结果,那么可以使用ContinueWith
,下面是代码示例:
注释掉上面的执行代码,添加如下代码,这些代码要添加在WriteLine($"{timer.ElapsedMilliseconds:#,##0}ms 流逝");
之前:
static decimal CallWebSevice()
{
WriteLine("开始调用web服务");
OutputThreadInfo();
Thread.Sleep((new Random()).Next(2000, 4000));
WriteLine("完成调用web服务");
return 89.99M;
}
static string CallStoreProcedure(decimal amount)
{
WriteLine("开始调用存储程序");
OutputThreadInfo();
Thread.Sleep((new Random()).Next(2000, 4000));
WriteLine("完成调用存储程序");
return $"12个产品的价格超过{amount:C}";
}
//继续执行另一任务
WriteLine("将一个任务的结果作为一个inut传给另一个任务");
Task<string> task = Task.Factory
.StartNew(CallWebSevice) //返回Task<decimal>
.ContinueWith(t => //返回Task<string>
CallStoreProcedure(t.Result));
WriteLine($"Result: {task.Result}");
运行结果: