赞
踩
在一个进程中同时启用多个线程并行操作,就叫做多线程
由CPU来自动处理
线程有运行、阻塞、就绪三态
代码示例:
class Program { static void Main(string[] args) { Thread thread = new Thread(() => { Print1(); }); thread.Start(); for(int i = 0; i < 1000; i++) { Console.Write(0); } Console.Read(); } static void Print1() { for (int i = 0;i < 1000; i++) { Console.Write(1); } } }
运行结果为
可以看到,在结果中,0和1的输出是交织在一起的,原因为两个线程交替着被运行,不断反复直到结束。
另外一个常用操作为Sleep();
让时间暂停,使得线程进入静默状态。
代码举例:
class Program { static void Main(string[] args) { Thread thread = new Thread(PrintHello); thread.Start(); Console.WriteLine("退出主程序"); } private static void PrintHello(object? obj) { while (true) { Thread.Sleep(1000); Console.WriteLine("Hello from PrintHello!"); } } }
运行会发现,即使主线程运行结束了,子线程依旧在持续运行;持续运行的子线程就称为前台线程。
一般来说,只有等待前台线程运行完毕后,程序才可以进行关闭。
与之对应的是 后台线程,可以通过thread.IsBackground = true;//切换为后台线程
将前台 线程切换到后台线程,这样再次运行会发现,当主线程结束后,后台线程就会被强制结束。
一般来说,前台线程用于需要时间比较长的等待业务,比如监听客户端请求,而后台线程适用于时较短的业务比如执行客户端发来的请求,后台进程不会影响程序的终止。
托管在线程池中的线程全部为后台线程。
所有使用new Thread创建的线程默认均为前台线程。
示例代码
for(int i = 0;i < 100; i++)
{
ThreadPool.QueueUserWorkItem((o) =>
{
Console.WriteLine($"循环次数{i} 线程id {Thread.CurrentThread.ManagedThreadId}");
});
}
可以看到执行结果出现了id重复的状况,原因就是线程池会重复使用已经完成的线程,极大节约硬件资源。
另外,可以看到,for循环有100次,但是从输出结果来看,只执行了十几次,原因为线程池创建的线程均为后台线程,只要主程序退出,线程池的后台线程就会被停止,而主程序main执行的时间很短,因此线程池内线程没有来得及执行就被停止了。
对于重要的并发量小的线程,需要手动创建管理,对于并发量大而又不太重要的线程,最好托管到线程池中。
不管程序有多少个进程,进程内部的资源都是被共享的。所以C#对进程的取消代码作了更高层次的抽象,把进程的取消过程封装成为了Token的形式,也就是CancellationToken(取消令牌)。不仅可以使用在多线程中,还可以用于异步操作。
class Program { static void Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource(); Thread thread = new Thread(() => { PrintHello(cts.Token); }); thread.Start(); //下载文件 Thread.Sleep(5000); //关闭子进程 //cts.Cancel(); cts.CancelAfter(3000);//在下载完成后3s失效 Console.WriteLine("退出主程序"); } private static void PrintHello(CancellationToken tokenSource) { while (!tokenSource.IsCancellationRequested) { Thread.Sleep(1000); Console.WriteLine("Hello from PrintHello!"); } } }
对于子线程执行时间不确定的情况,需要使用Join的方法,加入至主程序执行中,或者使用IsAlive方法进行判断
class Program { static void Main(string[] args) { Thread thread = new Thread(() => { PrintHello(); }); thread.Start(); //方法一 //thread.Join(); //方法二 while(thread.IsAlive) { Console.WriteLine("子线程仍在工作"); Thread.Sleep(100); } Console.WriteLine("退出主程序"); } private static void PrintHello() { int i = 0; while (i++ < 10) { Thread.Sleep(new Random().Next(100, 1000)); Console.WriteLine("Hello from PrintHello!"); } } }
使用线程可以并发的在CPU的核心中执行任务,最大化CPU的利用率,但是并发执行任务也可能产生各种各样的资源竞争问题。
举例:
private static void AddText()
{
File.AppendAllText(@"D:\test.txt", $"开始{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(100);
File.AppendAllText((@"D:\test.txt", $"结束{Thread.CurrentThread.ManagedThreadId}");
}
当两个线程同时需要使用同一个文件资源时,产生资源竞争,导致系统崩溃。
因此必须保证同一时刻只能有一个线程访问资源,避免出现资源恶性竞争。
使用线程锁就可以解决
class Program { static object lockedObj = new object(); static void Main(string[] args) { for(int i = 0; i < 10; i++) { var t = new Thread(AddText); t.Start(); } Console.WriteLine("退出主程序"); } private static void AddText() { lock(lockedObj) { File.AppendAllText(@"D:\test.txt", $"开始{Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(100); File.AppendAllText(@"D:\test.txt", $"结束{Thread.CurrentThread.ManagedThreadId}"); } } }
在之前项目中,我们实现的所有操作都是同步进行的,然而当有同时10000个请求发生时,会使得用户有很长的等待,服务器会等待数据库的响应,完成后反馈至用户。
而异步操作要实现,不要等待数据库,继续执行下一个请求,当数据返回数据以后,再回头继续处理上一个请求。
然而对于更高级别的数量请求,仅仅依靠异步也是不够的,因此需要:
异步服务+每个机器多开进程+多个机器组合实现;
K8s, Kubernetess容器化分布式部署;
.NET Core对容器化非常非常友好、支持度极高
我们使用异步处理并行,使用多线程处理并发。
异步逻辑是要基于方法没有依赖关系的,例如
class Program { static void Main(string[] args) { Calculate(); Console.Read(); } static void Calculate() { //Task->异步,Thread->线程 Task.Run(() => { Calculate1(); }); Task.Run(() => { Calculate2(); }); Task.Run(() => { Calculate3(); }); } static int Calculate1() { var result = 3; Console.WriteLine($"Calculate1: {result}"); Task.Delay(2000); return result; } static int Calculate2() { var result = 4; Console.WriteLine($"Calculate2: {result}"); Task.Delay(3000); return result; } static int Calculate3() { var result = 5; Console.WriteLine($"Calculate3: {result}"); Task.Delay(1000); return result; } }
但如果是具有依赖关系的,例如,Caculate2()需要Caculate1()的结果,Caculate3需要Caculate1()和Caculate2()的结果,那么就需要做如下调整
class Program { static void Main(string[] args) { Calculate(); Console.Read(); } static void Calculate() { //Task->异步,Thread->线程 var task1 = Task.Run(() => { return Calculate1(); }); var awaiter1 = task1.GetAwaiter();//获得异步等待对象 awaiter1.OnCompleted(() => { var result1 = awaiter1.GetResult();//获得异步逻辑的最终计算结果 var task2 = Task.Run(() => { return Calculate2(result1); }); var awaiter2 = task2.GetAwaiter(); awaiter2.OnCompleted(() => { var result2 = awaiter2.GetResult(); var result = Calculate3(result1, result2); Console.WriteLine(result); }); }); } static int Calculate1() { var result = 3; Console.WriteLine($"Calculate1: {result}"); Task.Delay(2000); return result; } static int Calculate2(int a) { var result = a * 2; Console.WriteLine($"Calculate2: {result}"); Task.Delay(3000); return result; } static int Calculate3(int a, int b) { var result = a + b; Console.WriteLine($"Calculate3: {result}"); Task.Delay(1000); return result; } }
可以看到,上面的异步操作代码非常复杂繁琐,接下来使用async/await化解上面操作
同步方法
指程序调用某个方法,需要等待执行完成以后才进行下一步操作
异步方法
指程序调用某个方法的时候,不做任何等待,在处理完成之前就返回该方法,继续执行接下来的操作,即函数在执行完成前就可以先返回调用方,然后继续执行接下来的逻辑完成任务的函数
举例:
public async Task<int> DoSomethingAsync()
{
//创建一个计算1万毫秒的任务
Task<int> longRunningTask = LongRunningTaskAsync();
//使用await执行这个任务
int result = await longRunningTask;
return result;
}
//假装计算1w毫秒,输出为1
private async Task<int> LongRunningTaskAsync()
{
await Task.Delay(10000);//延迟10s
return 1;
}
1.需要使用async关键词
2.返回类型为:void
、Task
、Task<T>
和 IAsyncEnumerable<T>
3.命名规范:Async结尾
4.需要有await表达式
5.要有返回值
6.async函数只能被async函数调用
共有三个部分:
第一部分异步调用:Task<int> longRunningTask = LongRunningTaskAsync();
第二部分执行异步:int result = await longRunningTask;
第三部分异步方法:private async Task<int> LongRunningTaskAsync(){}
注:
下面对之前的异步代码进行优化:
class Program { static void Main(string[] args) { Calculate(); Console.Read(); } static async void Calculate() { var result1 = await Calculate1Async(); var result2 = await Calculate2Async(result1); var result = await Calculate3Async(result1, result2); Console.WriteLine(result); } static async Task<int> Calculate1Async() { var result = 3; Console.WriteLine($"Calculate1: {result}"); await Task.Delay(2000); return result; } static async Task<int> Calculate2Async(int a) { var result = a * 2; Console.WriteLine($"Calculate2: {result}"); await Task.Delay(3000); return result; } static async Task<int> Calculate3Async(int a, int b) { var result = a + b; Console.WriteLine($"Calculate3: {result}"); await Task.Delay(1000); return result; } }
异步不是多线程!!!
异步用来处理并行,多线程用于处理并发
class Program { static void Main(string[] args) { TaskTest(); ThreadTest(); Console.Read(); } static void TaskTest() { var sw = new Stopwatch(); sw.Start(); for(int i = 0; i < 100; i++) { Task.Factory.StartNew(() => { }); } sw.Stop(); Console.WriteLine($"Task {sw.ElapsedMilliseconds}"); } static void ThreadTest() { var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100; i++) { new Thread(() => { }).Start(); } sw.Stop(); Console.WriteLine($"Thread {sw.ElapsedMilliseconds}"); } }
执行结果为
可以看到Task的执行速度要远高于Thread!
异步并不会创建线程,只是通过主线程来执行,同时开出一条分路来执行其他任务,非同步分别执行。但是在最后会创建一个非常轻量级的Worker Thread,用于通知主程序异步结束,也称为回调Call Back.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。