目录
四线程(基础篇3),二十八线程基础篇3
在上一篇十六线程(基础篇2)中,大家器重描述了分明线程的景况、线程优先级、前台线程和后台线程以及向线程传递参数的文化,在这一篇中大家将汇报怎么样使用C#的lock关键字锁定线程、使用Monitor锁定线程以及线程中的十分管理。
九、使用C#的lock关键字锁定线程
1、使用Visual Studio 二零一六开立贰个新的调整台应用程序。
2、双击打开“Program.cs”文件,然后修改为如下代码:
1 using System;
2 using System.Threading;
3 using static System.Console;
4
5 namespace Recipe09
6 {
7 abstract class CounterBase
8 {
9 public abstract void Increment();
10 public abstract void Decrement();
11 }
12
13 class Counter : CounterBase
14 {
15 public int Count { get; private set; }
16
17 public override void Increment()
18 {
19 Count++;
20 }
21
22 public override void Decrement()
23 {
24 Count--;
25 }
26 }
27
28 class CounterWithLock : CounterBase
29 {
30 private readonly object syncRoot = new Object();
31
32 public int Count { get; private set; }
33
34 public override void Increment()
35 {
36 lock (syncRoot)
37 {
38 Count++;
39 }
40 }
41
42 public override void Decrement()
43 {
44 lock (syncRoot)
45 {
46 Count--;
47 }
48 }
49 }
50
51 class Program
52 {
53 static void TestCounter(CounterBase c)
54 {
55 for (int i = 0; i < 100000; i++)
56 {
57 c.Increment();
58 c.Decrement();
59 }
60 }
61
62 static void Main(string[] args)
63 {
64 WriteLine("Incorrect counter");
65 var c1 = new Counter();
66 var t1 = new Thread(() => TestCounter(c1));
67 var t2 = new Thread(() => TestCounter(c1));
68 var t3 = new Thread(() => TestCounter(c1));
69 t1.Start();
70 t2.Start();
71 t3.Start();
72 t1.Join();
73 t2.Join();
74 t3.Join();
75 WriteLine($"Total count: {c1.Count}");
76
77 WriteLine("--------------------------");
78
79 WriteLine("Correct counter");
80 var c2 = new CounterWithLock();
81 t1 = new Thread(() => TestCounter(c2));
82 t2 = new Thread(() => TestCounter(c2));
83 t3 = new Thread(() => TestCounter(c2));
84 t1.Start();
85 t2.Start();
86 t3.Start();
87 t1.Join();
88 t2.Join();
89 t3.Join();
90 WriteLine($"Total count: {c2.Count}");
91 }
92 }
93 }
3、运行该调整台应用程序,运营效果(每回运维效果兴许分裂)如下图所示:
在第65行代码处,大家创立了Counter类的一个对象,该类定义了叁个差不离的counter变量,该变量能够自增1和自减1。然后在第66~68行代码处,我们创制了多少个线程,并利用lambda表明式将Counter对象传递给了“TestCounter”方法,那八个线程分享同多少个counter变量,何况对这几个变量实行自增和自减操作,那将招致结果的不科学。假若我们往往周转这一个调整台程序,它将打字与印刷出差别的counter值,有相当的大可能率是0,但超过十分之五景况下不是。
产生这种情状是因为Counter类是非线程安全的。我们只要第二个线程在第57行代码处试行实现后,还不曾实行第58行代码时,第一个线程也推行了第57行代码,那个时候counter的变量值自增了2次,然后,那多少个线程同时实践了第58行处的代码,那会招致counter的变量只自减了1次,由此,形成了不得法的结果。
为了保证不产生上述不得法的景观,我们必得保障在某贰个线程访问counter变量时,别的全部的线程必得等待其进行实现技术一连拜谒,大家得以选用lock关键字来变成那一个职能。就算大家在有个别线程中锁定三个目的,别的具有线程必需等到该线程解锁之后能力访问到这些指标,因而,能够幸免上述意况的发出。可是要小心的是,使用这种措施会严重影响程序的属性。更加好的办法大家将会在仙童一齐中呈报。
十、使用Monitor锁定线程
在这一小节中,大家将叙述一个十二线程编制程序中的常见的一个难题:死锁。我们先是创制贰个死锁的示范,然后使用Monitor制止死锁的发生。
1、使用Visual Studio 二〇一六成立贰个新的调控台应用程序。
澳门在线威尼斯官方 ,2、双击张开“Program.cs”文件,编写代码如下:
1 using System;
2 using System.Threading;
3 using static System.Console;
4 using static System.Threading.Thread;
5
6 namespace Recipe10
7 {
8 class Program
9 {
10 static void LockTooMuch(object lock1, object lock2)
11 {
12 lock (lock1)
13 {
14 Sleep(1000);
15 lock (lock2)
16 {
17 }
18 }
19 }
20
21 static void Main(string[] args)
22 {
23 object lock1 = new object();
24 object lock2 = new object();
25
26 new Thread(() => LockTooMuch(lock1, lock2)).Start();
27
28 lock (lock2)
29 {
30 WriteLine("This will be a deadlock!");
31 Sleep(1000);
32 lock (lock1)
33 {
34 WriteLine("Acquired a protected resource succesfully");
35 }
36 }
37 }
38 }
39 }
3、运营该调节台应用程序,运转效果如下图所示:
在上述结果中我们能够看来程序爆发了死锁,程序一贯截至不了。
在第10~19行代码处,咱们定义了三个名称叫“LockTooMuch”的方法,在该方法中大家锁定了第叁个目的lock1,等待1秒钟后,希望锁定第二个对象lock2。
在第26行代码处,我们创制了一个新的线程来施行“LockTooMuch”方法,然后立时进行第28行代码。
在第28~32行代码处,我们在主线程中锁定了对象lock2,然后等待1分钟后,希望锁定第叁个目的lock1。
在开立的新线程中大家锁定了指标lock1,等待1分钟,希望锁定指标lock2,而这年对象lock2已经被主线程锁定,所以新建线程会等待对象lock2被主线程解锁。可是,在主线程中,大家锁定了目的lock2,等待1分钟,希望锁定指标lock1,而那一年对象lock1已经被创设的线程锁定,所以主线程会等待对象lock1被创立的线程解锁。当产生这种情景的时候,死锁就生出了,所以大家的调节台应用程序近期不能正常结束。
4、要幸免死锁的发生,大家得以行使“Monitor.TryEnter”方法来替换lock关键字,“Monitor.TryEnter”方法在伸手不到财富时不会卡住等待,能够安装超时时间,获取不到一贯回到false。修改代码如下所示:
1 using System;
2 using System.Threading;
3 using static System.Console;
4 using static System.Threading.Thread;
5
6 namespace Recipe10
7 {
8 class Program
9 {
10 static void LockTooMuch(object lock1, object lock2)
11 {
12 lock (lock1)
13 {
14 Sleep(1000);
15 lock (lock2)
16 {
17 }
18 }
19 }
20
21 static void Main(string[] args)
22 {
23 object lock1 = new object();
24 object lock2 = new object();
25
26 new Thread(() => LockTooMuch(lock1, lock2)).Start();
27
28 lock (lock2)
29 {
30 WriteLine("This will be a deadlock!");
31 Sleep(1000);
32 //lock (lock1)
33 //{
34 // WriteLine("Acquired a protected resource succesfully");
35 //}
36 if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
37 {
38 WriteLine("Acquired a protected resource succesfully");
39 }
40 else
41 {
42 WriteLine("Timeout acquiring a resource!");
43 }
44 }
45 }
46 }
47 }
5、运维该调节台应用程序,运转作效果果如下图所示:
此时,我们的调整台应用程序就幸免了死锁的发生。
十一、管理特别
在这一小节中,我们陈述如何在线程中正确地拍卖极其。准确地将try/catch块放置在线程内部是特别重大的,因为在线程外界捕获线程内部的不得了平日是不大概的。
1、使用Visual Studio 二〇一五创办贰个新的调控台应用程序。
2、双击张开“Program.cs”文件,修改代码如下所示:
1 using System;
2 using System.Threading;
3 using static System.Console;
4 using static System.Threading.Thread;
5
6 namespace Recipe11
7 {
8 class Program
9 {
10 static void BadFaultyThread()
11 {
12 WriteLine("Starting a faulty thread...");
13 Sleep(TimeSpan.FromSeconds(2));
14 throw new Exception("Boom!");
15 }
16
17 static void FaultyThread()
18 {
19 try
20 {
21 WriteLine("Starting a faulty thread...");
22 Sleep(TimeSpan.FromSeconds(1));
23 throw new Exception("Boom!");
24 }
25 catch(Exception ex)
26 {
27 WriteLine($"Exception handled: {ex.Message}");
28 }
29 }
30
31 static void Main(string[] args)
32 {
33 var t = new Thread(FaultyThread);
34 t.Start();
35 t.Join();
36
37 try
38 {
39 t = new Thread(BadFaultyThread);
40 t.Start();
41 }
42 catch (Exception ex)
43 {
44 WriteLine(ex.Message);
45 WriteLine("We won't get here!");
46 }
47 }
48 }
49 }
3、运转该调节台应用程序,运维效果如下图所示:
在第10~15行代码处,我们定义了二个名叫“BadFaultyThread”的艺术,在该措施中抛出贰个可怜,並且未有选用try/catch块捕获该非常。
在第17~29行代码处,大家定义了二个名字为“FaultyThread”的点子,在该办法中也抛出贰个十二分,不过大家使用了try/catch块捕获了该极度。
在第33~35行代码处,我们创制了三个线程,在该线程中进行了“FaultyThread”方法,我们能够见到在这么些新制造的线程中,大家科学地捕获了在“FaultyThread”方法中抛出的极其。
在第37~46行代码处,我们又新创立了多个线程,在该线程中实践了“BadFaultyThread”方法,并且在主线程中应用try/catch块来捕获在新创制的线程中抛出的要命,不幸的的是我们在主线程中不恐怕捕获在新线程中抛出的可怜。
因而能够看出,在两个线程中抓获另多个线程中的十分平常是不可行的。
至此,二十多线程(基础篇)我们就陈诉到此刻,之后大家将汇报线程同步相关的文化,敬请期待!
源码下载
在上一篇二十二十四线程(基础篇2)中,大家第一描述了鲜明线程的情况、线程优先级、前台线程和后台线程以...
1.10 C# Lock关键字的应用
在多线程的系统中,由于CPU的日子片轮转等线程调解算法的运用,轻易并发线程安全难题。具体可参考《深入理解计算机系统》
一书相关的章节。
在C#中lock
首要字是一个语法糖,它将Monitor
打包,给object加上贰个互斥锁,进而完成代码的线程安全,Monitor
会在下一节中牵线。
对于lock
关键字照旧Monitor
锁定的对象,都必需小心采用,不适于的精选也许会形成惨烈的属性难点照旧发出死锁。以下有几条有关接纳锁定目的的提出。
- 联机锁定的目的不可能是值类型。因为使用值类型时会有装箱的难题,装箱后的就成了一个新的实例,会造成
Monitor.Enter()
和Monitor.Exit()
采用到分歧的实例而失去关联性- 制止锁定
this、typeof(type)和string
。this
和typeof(type)
锁定只怕在别的不相干的代码中会有平等的概念,导致三个共同块相互阻塞。string
急需思考字符串拘禁的标题,要是同一个字符串常量在四个地点出现,恐怕引用的会是同一个实例。- 对象的选料作用域尽恐怕刚好达到供给,使用静态的、私有的变量。
以下演示代码达成了多线程情状下的计数效能,一种实现是线程不安全的,会形成结果与预期不切合,但也可以有十分大希望准确。另外一种采纳了lock
根本字张开线程同步,所以它结果是必定的。
static void Main(string[] args)
{
Console.WriteLine("错误的多线程计数方式");
var c = new Counter();
// 开启3个线程,使用没有同步块的计数方式对其进行计数
var t1 = new Thread(() => TestCounter(c));
var t2 = new Thread(() => TestCounter(c));
var t3 = new Thread(() => TestCounter(c));
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
// 因为多线程 线程抢占等原因 其结果是不一定的 碰巧可能为0
Console.WriteLine($"Total count: {c.Count}");
Console.WriteLine("--------------------------");
Console.WriteLine("正确的多线程计数方式");
var c1 = new CounterWithLock();
// 开启3个线程,使用带有lock同步块的方式对其进行计数
t1 = new Thread(() => TestCounter(c1));
t2 = new Thread(() => TestCounter(c1));
t3 = new Thread(() => TestCounter(c1));
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
// 其结果是一定的 为0
Console.WriteLine($"Total count: {c1.Count}");
Console.ReadLine();
}
static void TestCounter(CounterBase c)
{
for (int i = 0; i < 100000; i++)
{
c.Increment();
c.Decrement();
}
}
// 线程不安全的计数
class Counter : CounterBase
{
public int Count { get; private set; }
public override void Increment()
{
Count++;
}
public override void Decrement()
{
Count--;
}
}
// 线程安全的计数
class CounterWithLock : CounterBase
{
private readonly object _syncRoot = new Object();
public int Count { get; private set; }
public override void Increment()
{
// 使用Lock关键字 锁定私有变量
lock (_syncRoot)
{
// 同步块
Count++;
}
}
public override void Decrement()
{
lock (_syncRoot)
{
Count--;
}
}
}
abstract class CounterBase
{
public abstract void Increment();
public abstract void Decrement();
}
运作结果如下图所示,与预期结果符合。
- C#多线程编制程序类别(二)-
线程基础
- 1.1 简介
- 1.2 创造线程
- 1.3 暂停线程
- 1.4 线程等待
- 1.5 终止线程
- 1.6 检查评定线程状态
- 1.7 线程优先级
- 1.8 前台线程和后台线程
- 1.9 向线程传递参数
- 1.10 C# Lock关键字的利用
- 1.11 使用Monitor类锁定财富
- 1.12 多线程中拍卖极其
- 参照书籍
- 笔者水平有限,假设不当招待各位评论指正!
1.12 二十四线程中管理特别
在十六线程中拍卖特别应当利用前后原则,在哪些线程发生分外那么所在的代码块确定要有对应的不胜管理。不然可能会产生程序崩溃、数据错过。
主线程中选取try/catch
话语是不能够捕获制造线程中的相当。然而万一蒙受不可预料的可怜,可因此监听AppDomain.CurrentDomain.UnhandledException
事件来张开捕获和那多少个处理。
示范代码如下所示,相当管理 1 和 卓殊管理 2 能平常被推行,而非常管理 3 是于事无补的。
static void Main(string[] args)
{
// 启动线程,线程代码中进行异常处理
var t = new Thread(FaultyThread);
t.Start();
t.Join();
// 捕获全局异常
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
t = new Thread(BadFaultyThread);
t.Start();
t.Join();
// 线程代码中不进行异常处理,尝试在主线程中捕获
AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
try
{
t = new Thread(BadFaultyThread);
t.Start();
}
catch (Exception ex)
{
// 永远不会运行
Console.WriteLine($"异常处理 3 : {ex.Message}");
}
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Console.WriteLine($"异常处理 2 :{(e.ExceptionObject as Exception).Message}");
}
static void BadFaultyThread()
{
Console.WriteLine("有异常的线程已启动...");
Thread.Sleep(TimeSpan.FromSeconds(2));
throw new Exception("Boom!");
}
static void FaultyThread()
{
try
{
Console.WriteLine("有异常的线程已启动...");
Thread.Sleep(TimeSpan.FromSeconds(1));
throw new Exception("Boom!");
}
catch (Exception ex)
{
Console.WriteLine($"异常处理 1 : {ex.Message}");
}
}
运作结果如下图所示,与预期结果同样。
1.11 使用Monitor类锁定财富
Monitor
类主要用来线程同步中,
lock
首要字是对Monitor
类的二个打包,其卷入结构如下代码所示。
try
{
Monitor.Enter(obj);
dosomething();
}
catch(Exception ex)
{
}
finally
{
Monitor.Exit(obj);
}
以下代码演示了使用Monitor.TyeEnter()
办法制止能源死锁和选用lock
发生产资料源死锁的意况。
static void Main(string[] args)
{
object lock1 = new object();
object lock2 = new object();
new Thread(() => LockTooMuch(lock1, lock2)).Start();
lock (lock2)
{
Thread.Sleep(1000);
Console.WriteLine("Monitor.TryEnter可以不被阻塞, 在超过指定时间后返回false");
// 如果5S不能进入同步块,那么返回。
// 因为前面的lock锁定了 lock2变量 而LockTooMuch()一开始锁定了lock1 所以这个同步块无法获取 lock1 而LockTooMuch方法内也不能获取lock2
// 只能等待TryEnter超时 释放 lock2 LockTooMuch()才会是释放 lock1
if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
{
Console.WriteLine("获取保护资源成功");
}
else
{
Console.WriteLine("获取资源超时");
}
}
new Thread(() => LockTooMuch(lock1, lock2)).Start();
Console.WriteLine("----------------------------------");
lock (lock2)
{
Console.WriteLine("这里会发生资源死锁");
Thread.Sleep(1000);
// 这里必然会发生死锁
// 本同步块 锁定了 lock2 无法得到 lock1
// 而 LockTooMuch 锁定了 lock1 无法得到 lock2
lock (lock1)
{
// 该语句永远都不会执行
Console.WriteLine("获取保护资源成功");
}
}
}
static void LockTooMuch(object lock1, object lock2)
{
lock (lock1)
{
Thread.Sleep(1000);
lock (lock2) ;
}
}
运作结果如下图所示,因为使用Monitor.TryEnter()
措施在逾期从此会回到,不会堵塞线程,所以未有产生死锁。而第二段代码中lock
从没过期重临的功效,导致能源死锁,同步块中的代码恒久不会被试行。
1.9 向线程传递参数
向线程中传递参数常用的有三种形式,构造函数字传送值、Start方法传值和拉姆da表明式传值,一般常用Start方法来传值。
亲自过问代码如下所示,通过二种方法来传递参数,告诉线程中的循环最后须求循环三回。
static void Main(string[] args)
{
// 第一种方法 通过构造函数传值
var sample = new ThreadSample(10);
var threadOne = new Thread(sample.CountNumbers);
threadOne.Name = "ThreadOne";
threadOne.Start();
threadOne.Join();
Console.WriteLine("--------------------------");
// 第二种方法 使用Start方法传值
// Count方法 接收一个Object类型参数
var threadTwo = new Thread(Count);
threadTwo.Name = "ThreadTwo";
// Start方法中传入的值 会传递到 Count方法 Object参数上
threadTwo.Start(8);
threadTwo.Join();
Console.WriteLine("--------------------------");
// 第三种方法 Lambda表达式传值
// 实际上是构建了一个匿名函数 通过函数闭包来传值
var threadThree = new Thread(() => CountNumbers(12));
threadThree.Name = "ThreadThree";
threadThree.Start();
threadThree.Join();
Console.WriteLine("--------------------------");
// Lambda表达式传值 会共享变量值
int i = 10;
var threadFour = new Thread(() => PrintNumber(i));
i = 20;
var threadFive = new Thread(() => PrintNumber(i));
threadFour.Start();
threadFive.Start();
}
static void Count(object iterations)
{
CountNumbers((int)iterations);
}
static void CountNumbers(int iterations)
{
for (int i = 1; i <= iterations; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(0.5));
Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
}
}
static void PrintNumber(int number)
{
Console.WriteLine(number);
}
class ThreadSample
{
private readonly int _iterations;
public ThreadSample(int iterations)
{
_iterations = iterations;
}
public void CountNumbers()
{
for (int i = 1; i <= _iterations; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(0.5));
Console.WriteLine($"{Thread.CurrentThread.Name} prints {i}");
}
}
}
运行结果如下图所示,与预期结果符合。
C#十二线程编制程序连串(二)- 线程基础
1.6 检验线程状态
线程的情事可由此寻访ThreadState
性格来检查评定,ThreadState
是二个枚举类型,一共有10种情状,状态具体意思如下表所示。
成员名称 | 说明 |
---|---|
Aborted | 线程处于 Stopped 状态中。 |
AbortRequested | 已对线程调用了 Thread.Abort 方法,但线程尚未收到试图终止它的挂起的 System.Threading.ThreadAbortException。 |
Background | 线程正作为后台线程执行(相对于前台线程而言)。此状态可以通过设置 Thread.IsBackground 属性来控制。 |
Running | 线程已启动,它未被阻塞,并且没有挂起的 ThreadAbortException。 |
Stopped | 线程已停止。 |
StopRequested | 正在请求线程停止。这仅用于内部。 |
Suspended | 线程已挂起。 |
SuspendRequested | 正在请求线程挂起。 |
Unstarted | 尚未对线程调用 Thread.Start 方法。 |
WaitSleepJoin | 由于调用 Wait、Sleep 或 Join,线程已被阻止。 |
下表列出导致情形退换的操作。
操作 | ThreadState |
---|---|
在公共语言运行库中创建线程。 | Unstarted |
线程调用 Start | Unstarted |
线程开始运行。 | Running |
线程调用 Sleep | WaitSleepJoin |
线程对其他对象调用 Wait。 | WaitSleepJoin |
线程对其他线程调用 Join。 | WaitSleepJoin |
另一个线程调用 Interrupt | Running |
另一个线程调用 Suspend | SuspendRequested |
线程响应 Suspend 请求。 | Suspended |
另一个线程调用 Resume | Running |
另一个线程调用 Abort | AbortRequested |
线程响应 Abort 请求。 | Stopped |
线程被终止。 | Stopped |
演示代码如下所示:
static void Main(string[] args)
{
Console.WriteLine("开始执行...");
Thread t = new Thread(PrintNumbersWithStatus);
Thread t2 = new Thread(DoNothing);
// 使用ThreadState查看线程状态 此时线程未启动,应为Unstarted
Console.WriteLine($"Check 1 :{t.ThreadState}");
t2.Start();
t.Start();
// 线程启动, 状态应为 Running
Console.WriteLine($"Check 2 :{t.ThreadState}");
// 由于PrintNumberWithStatus方法开始执行,状态为Running
// 但是经接着会执行Thread.Sleep方法 状态会转为 WaitSleepJoin
for (int i = 1; i < 30; i++)
{
Console.WriteLine($"Check 3 : {t.ThreadState}");
}
// 延时一段时间,方便查看状态
Thread.Sleep(TimeSpan.FromSeconds(6));
// 终止线程
t.Abort();
Console.WriteLine("t线程被终止");
// 由于该线程是被Abort方法终止 所以状态为 Aborted或AbortRequested
Console.WriteLine($"Check 4 : {t.ThreadState}");
// 该线程正常执行结束 所以状态为Stopped
Console.WriteLine($"Check 5 : {t2.ThreadState}");
Console.ReadKey();
}
static void DoNothing()
{
Thread.Sleep(TimeSpan.FromSeconds(2));
}
static void PrintNumbersWithStatus()
{
Console.WriteLine("t线程开始执行...");
// 在线程内部,可通过Thread.CurrentThread拿到当前线程Thread对象
Console.WriteLine($"Check 6 : {Thread.CurrentThread.ThreadState}");
for (int i = 1; i < 10; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
Console.WriteLine($"t线程输出 :{i}");
}
}
运作结果如下图所示,与预期的结果同样。
1.4 线程等待
在本章中,线程等待使用的是Join
方法,该格局将暂停实行当前线程,直到所等待的另贰个线程终止。在简练的线程同步中会使用到,但它比较轻松,不作过多介绍。
亲自去做代码如下所示:
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"-------开始执行 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");
// 1.创建一个线程 PrintNumbersWithDelay为该线程所需要执行的方法
Thread t = new Thread(PrintNumbersWithDelay);
// 2.启动线程
t.Start();
// 3.等待线程结束
t.Join();
Console.WriteLine($"-------执行完毕 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}-------");
// 暂停一下
Console.ReadKey();
}
static void PrintNumbersWithDelay()
{
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
for (int i = 0; i < 10; i++)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString("HH:mm:ss.ffff")}");
}
}
}
运作结果如下图所示,开端进行和进行实现两条音讯由主线程打字与印刷;依据其出口的依次可知主线程是等待其他的线程停止后才输出试行完成那条消息。
本文由澳门在线威尼斯官方发布于澳门在线威尼斯官方,转载请注明出处:多线程编程系列,多线程基础篇3
关键词: