前言:
多线程无处不在,应该是正常开发过程中最常用的基础技术之一。下面通过Thread、ThreadPool、Task、Parallel、线程锁、线程取消等分步演示多线程的一些基本操作,欢迎大家交流。如果各位大佬还有其他关于多线程的扩展,也欢迎在评论区留言。老板们的知识互助是.net生态发展的重要组成部分。欢迎各位老板留言,帮助更多人。
为防止爬虫,本文特此放原文链接。如果在其他地方(博客园和CSDN除外)搜索,可以点击以下链接跳转回原文:
以下博客内容使用的一些环境:
系统环境:WIN 10
.NET 环境:.NET 6
VS 环境:VS 2022
其他:没有
以下文字:
1、创建一个 .NET 6 控制台项目以用作此博客文章的实验。
2、快速创建线程。ParameterizedThreadStart 是一个委托,传入的参数是一个对象类型。
代码:
ParameterizedThreadStart threadStart = new((obj) => {
Console.WriteLine($"当前线程 的 ID = {Thread.CurrentThread.ManagedThreadId}");
});
Thread thread = new Thread(threadStart);
thread.Start();
Console.WriteLine($"线程ID = {thread.ManagedThreadId}");
Console.ReadLine();
4、新建一个TestThread类和一个测试方法进行测试。
5、在程序中,改变输出调用上面的方法,测试一下。
6、执行后的输出结果,如下图
7、线程等待(休眠)。最简单的方法是直接 Thread.Sleep(milliseconds);
8、线程的 Join 方法。代表线程执行完成后,可以继续执行后续代码。如下图所示,线程线程执行完毕后,很快就会执行最终的打印输出方法。输出结果可以与上面的第 7 点进行比较。
9、Thread的Join方法也可以传入参数,单位是毫秒。表示等待当前线程执行的时间。如果超过设定的毫秒数,则不等待,直接执行后续代码。
10、添加一个Test2方法来测试线程池ThreadPool的使用情况。
11、WaitCallback 也是一个委托。传入线程池中需要执行的方法名。在下面的代码中,“线程池”字符串就是执行方法对应的参数。
代码:
using MultiThread;
Console.WriteLine("Hello, World!");
ThreadPool.QueueUserWorkItem(new WaitCallback(TestThread.Test2),"线程池");
Console.ReadLine();
12、除了直接传入回调方法外,还可以在线程池打开的方法中直接编写代码块作为多线程执行的一部分。如下图,sleep 1000ms,执行的方法在线程池中运行。
13、在线程池中,可以设置Manual semaphore来识别线程池中的线程何时执行。一般来说,.Set(); 和.WaitOne(); 成对使用,如下图代码、注释部分和执行结果。(可以比较输出时间)
14、使用任务快速阅读创建一个线程。如下所示。最简单的方法:Task.Run(()=>{ block; });
15、也可以通过如下方式手动启动,如图中代码所示。
16、 也可以使用Task.Factory来实现,创建一个任务工厂。
17、如果需要等待子线程执行完毕再进行后续操作,可以使用Wait();实现它。
18、如果只想等待子线程执行指定时间,可以使用Wait(milliseconds); 达到。这样等待,比如500ms后,不管子线程是否还在waves中,都不会等待,直接继续执行后续代码。
19、如果要在等待一段时间后执行一些动作,可以使用Task.Delay(time in milliseconds).ContinuwWith(要执行的代码块);代码、注释和运行输出如下图所示。
20、如果执行期间有多个任务,任意一个线程执行完某个操作后,可以使用ContinueWhenAny来做。代码、注释和运行结果如下图所示,源代码附在图中。
代码:
21、如果要在所有任务执行完之后再执行一段代码,可以使用ContinueWhenAll。
22、使用TaskWaitAny()还可以实现任意任务执行后的后续动作。但是会占用主线程资源。代码如图,大佬们应该能看出原因。
23、 同样,Task 也可以在等待所有任务执行完毕后执行后续动作。如下图所示。
24、Parallel 允许线程并行执行。同时最大线程执行数和ThreadPool类似,可以设置最大并发线程数。不多说,看下面的代码和演示效果。
代码:
using MultiThread;
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>>Hello, World!");
ParallelOptions parallelOptions = new();
parallelOptions.MaxDegreeOfParallelism = 3;
Parallel.Invoke(parallelOptions,
() =>
{
Thread.Sleep(1000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> para1");
},
() =>
{
Thread.Sleep(2000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> para2");
},
() =>
{
Thread.Sleep(3000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> para3");
});
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 我不晓得要打印啥子 ~ ~ ");
Console.ReadLine();
25、并行也可以遍历。
代码:
using MultiThread;
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>>Hello, World!");
ParallelOptions parallelOptions = new();
parallelOptions.MaxDegreeOfParallelism = 3;
Parallel.For(0, 10,parallelOptions, s =>
{
Thread.Sleep(100);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> para{s}");
});
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 我不晓得要打印啥子 ~ ~ ");
Console.ReadLine();
26、增加一个测试使用多线程锁的方法。
27、 不加锁地执行以下代码,方法体几乎同时执行。但实际上,如果方法体只允许一个线程同时访问,那肯定是乱七八糟的,所以需要加锁。
28、加完锁后,查看执行结果,时间间隔基本在1s左右,说明方法体确实一次只被一个线程调用。
29、另一种锁(原子锁),可以定义一个变量进行原子交换。它的使用场景一般是在轮询处理一些业务时,同时只允许一个线程进来,可以使用这个锁。
与锁锁不同:锁锁是指代码还没有被执行,线程会一直等待,执行后会继续进来。如果一直在创建线程,就会有越来越多的线程和资源在锁外堆积。在最坏的情况下,系统内存会继续飙升,直到填满;原子锁的作用是验证代码块是否已经执行。执行完成后,他不会被鸟,线程也不会等待,而是直接跳过这部分代码,继续执行后续操作。如果接下来没有什么可做的,那该怎么办。
30、原子锁的执行效果如下。有些线程判断代码被锁定并跳过,所以不会有输出。
31、测试线程被取消。首先启动一些线程和相关操作,如下图所示。
32、然后执行。结果很尴尬,显示的是线程100。这是因为Task是多线程的。在创建过程中,i可能已经执行到最后,所以再次得到的i是最后一个值,即100.
33、在创建任务之前,引入一个中间变量来代替遍历的i。然后执行结果和其他代码描述,如图所示。
34、如果看不到异常信息,改成Task直接去,然后使用Task.WaitAll(); 捕获异常信息。如代码注释和演示截图所示。
代码:
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>>Hello, World!");
try
{
Task[] tasks = new Task[100];
CancellationTokenSource cancellation = new CancellationTokenSource();
for (int i = 0; i < 100; i++)
{
string str = i.ToString();
tasks[i]= Task.Run(() =>
{
Thread.Sleep(100);
try
{
if (str == "10")
{
throw new Exception($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 第 -{str}- 号线程开始放弃治疗~~ 线程ID = {Thread.CurrentThread.ManagedThreadId}");
}
}
catch (Exception ex)
{
cancellation.Cancel(); // 捕获异常,线程后续所有的线程都取消操作
Console.WriteLine(ex.Message);
}
cancellation.Token.ThrowIfCancellationRequested();
if (cancellation.IsCancellationRequested == false) // 默认为false,代表正常
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 第 -{str}- 号线程执行正常~~ 线程ID = {Thread.CurrentThread.ManagedThreadId}");
}
else
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 第 -{str}- 号线程执行异常~~ 线程ID = {Thread.CurrentThread.ManagedThreadId}");
}
}, cancellation.Token);
}
Task.WaitAll(tasks);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> 我不晓得要打印啥子 ~ ~ ");
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")} >>> {ex.Message}");
}
}
Console.ReadLine();
35、以上就是这篇部分文章的全部内容。如果对你有帮助,请点赞、转发或留言。如需转发,请注明出处:
如果你想和我一起吹嘘生活和技术,或者和其他朋友一起,你也可以扫描下面的二维码添加我的微信朋友,我不介意:
或者你也可以点击底部的QQ群链接加入文章原[]中的QQ群,我不介意。
不,最后祝大家编码愉快~~
暂无评论内容