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}");
运行结果:
作者:sq800
出处:https://www.cnblogs.com/sq800/p/17338942.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通